From d73240c295c46d2f53e63e562098bc2da222ce85 Mon Sep 17 00:00:00 2001 From: Dara Adedeji Date: Thu, 1 Oct 2026 19:28:37 -0400 Subject: [PATCH 01/14] Store each lineup in one cloud row A lineup synced as an origin row, a landing row and a link row, and the server checked across them: a link needed live ends (LINEUP_LINK_END_MISSING), an end could not go while a link used it (LINEUP_END_IN_USE), and clients opted in to both checks with applyBatch flags. The conflict unit was one part while the user's lineup needs all three. A lineup row now holds the whole lineup, kind "lineup", keyed by its id: name, video, notes, images, and full copies of its origin and landing. Lineups sharing a spot each carry their own copy; the client draws them as one spot again. Every row stands on its own, so the cross-row checks, their flags and error codes, and the endpoint index go. Page ownership (LINEUP_PAGE_MISMATCH) and strategy-scoped keys stay. Duplicating a strategy copies each row under fresh ids from one shared map, so copied lineups that shared a spot still share it. The agent summary counts each origin once per page, read from live lineups through a new by_strategyId_and_deleted index. Image references read data.images of any lineup row. Protocol 5: a client on 4 cannot read the new rows and writes rows the server no longer takes, so it is refused until it reloads. Production holds no lineup rows (checked 2026-10-01) and dev's lineups table is empty, so the narrowed schema needs no backfill. Co-Authored-By: Claude Opus 5.5 (1M context) --- convex/error_codes.json | 2 - convex/function_spec.json | 156671 +++++++++----------------- convex/imageAssetLifecycle.test.ts | 44 +- convex/lib/cloudProtocol.ts | 6 +- convex/lib/errors.ts | 2 - convex/lib/imageAssets.ts | 36 +- convex/lib/payloadValidators.ts | 35 +- convex/lib/strategyAgentSummary.ts | 57 +- convex/lineupGraph.test.ts | 1014 - convex/lineups.test.ts | 809 + convex/mediaLimits.test.ts | 144 +- convex/ops.ts | 210 +- convex/pageTrash.test.ts | 55 +- convex/referencedAssetIds.test.ts | 14 +- convex/schema.ts | 17 +- convex/strategies.ts | 91 +- convex/strategyDuplicate.test.ts | 313 +- convex/syncBoundaries.test.ts | 112 +- convex/testContent.helpers.ts | 49 + 19 files changed, 56782 insertions(+), 102899 deletions(-) delete mode 100644 convex/lineupGraph.test.ts create mode 100644 convex/lineups.test.ts diff --git a/convex/error_codes.json b/convex/error_codes.json index 2bb029f9..0ea83da9 100644 --- a/convex/error_codes.json +++ b/convex/error_codes.json @@ -16,8 +16,6 @@ "INVALID_PAYLOAD", "INVITE_EXPIRED", "INVITE_REVOKED", - "LINEUP_END_IN_USE", - "LINEUP_LINK_END_MISSING", "LINEUP_PAGE_MISMATCH", "LINEUP_STRATEGY_MISMATCH", "MISSING_ADD_ELEMENT_ARGS", diff --git a/convex/function_spec.json b/convex/function_spec.json index fdd2f771..6b04ef03 100644 --- a/convex/function_spec.json +++ b/convex/function_spec.json @@ -41418,107 +41418,662 @@ }, "payload": { "fieldType": { - "type": "union", - "value": [ - { - "type": "object", - "value": { - "data": { + "type": "object", + "value": { + "data": { + "fieldType": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { "fieldType": { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "boolean" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "number" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -41687,37 +42242,343 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "boolean" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "number" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -41784,18 +42645,115 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -41812,9 +42770,65 @@ } ] }, - { - "type": "array", - "value": { + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -41830,76 +42844,25 @@ "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "optional": false } - ] - }, - "optional": false - } + } + ] + }, + "optional": false } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -42068,15 +43031,118 @@ } } ] - } + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, { - "keys": { - "type": "string" + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -42102,6 +43168,102 @@ "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -42118,9 +43280,167 @@ } ] }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -42136,47 +43456,195 @@ "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "optional": false } - ] - } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, { - "keys": { - "type": "string" + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -42193,121 +43661,24 @@ } ] }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false + } + } + ] + }, + "optional": false + } + } + ] } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" }, { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -42476,40 +43847,137 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -42526,9 +43994,65 @@ } ] }, - { - "type": "array", - "value": { + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -42544,47 +44068,93 @@ "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "optional": false } - ] - } + } + ] }, - { - "keys": { - "type": "string" + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -42601,116 +44171,19 @@ } ] }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false + } + } + ] } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -42777,331 +44250,25 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false } - ] - } + } + ] }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false + } + } + ] } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -43474,37 +44641,321 @@ } } ] - } + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + }, + "optional": false + }, + "kind": { + "fieldType": { + "type": "literal", + "value": "lineup" + }, + "optional": false + }, + "payloadVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + "optional": false + }, + "publicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "revision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "sortIndex": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "updatedAt": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + } + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "clientProtocolVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Query", + "identifier": "lineups.js:listForStrategy", + "returns": { + "type": "array", + "value": { + "type": "object", + "value": { + "createdAt": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "deleted": { + "fieldType": { + "type": "boolean" + }, + "optional": false + }, + "pagePublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "payload": { + "fieldType": { + "type": "object", + "value": { + "data": { + "fieldType": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -43530,67 +44981,118 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] } @@ -43605,67 +45107,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -43707,67 +45158,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -43782,67 +45182,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -43938,67 +45287,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -44013,67 +45311,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -44115,67 +45362,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -44190,67 +45386,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -44319,67 +45464,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -44394,67 +45488,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -44496,67 +45539,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -44571,67 +45563,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -44654,157 +45595,262 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "lineupOrigin" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", "value": [ { "type": "null" @@ -44947,37 +45993,139 @@ } } ] - } + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -45044,122 +46192,20 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -45328,15 +46374,118 @@ } } ] - } + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, { - "keys": { - "type": "string" + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -45362,67 +46511,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -45437,67 +46535,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -45505,91 +46552,192 @@ } } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } - } - ] + ] + } }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { + { + "keys": { "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -45656,18 +46804,115 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -45684,92 +46929,41 @@ } ] }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false + } + } + ] } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + "type": "array", + "value": { "type": "union", "value": [ { @@ -45785,10 +46979,15 @@ "type": "string" } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -45804,47 +47003,93 @@ "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "optional": false } - ] - } + } + ] }, - { - "keys": { - "type": "string" + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -45861,116 +47106,19 @@ } ] }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false + } + } + ] } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -46037,95 +47185,96 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false } - ] - } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, { - "keys": { - "type": "string" + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -46151,6 +47300,102 @@ "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -46167,65 +47412,167 @@ } ] }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "null" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -46242,126 +47589,29 @@ } ] }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -46734,357 +47984,1013 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + }, + "optional": false + }, + "kind": { + "fieldType": { + "type": "literal", + "value": "lineup" + }, + "optional": false + }, + "payloadVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + "optional": false + }, + "publicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "revision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "sortIndex": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "updatedAt": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + } + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "paginationOpts": { + "fieldType": { + "type": "object", + "value": { + "cursor": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "endCursor": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "optional": true + }, + "id": { + "fieldType": { + "type": "number" + }, + "optional": true + }, + "maximumBytesRead": { + "fieldType": { + "type": "number" + }, + "optional": true + }, + "maximumRowsRead": { + "fieldType": { + "type": "number" + }, + "optional": true + }, + "numItems": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + "optional": false + }, + "table": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "literal", + "value": "elements" + }, + { + "type": "literal", + "value": "lineups" + } + ] + }, + "optional": false + } + } + }, + "functionType": "Mutation", + "identifier": "maintenance.js:backfillAssetReferences", + "returns": null, + "visibility": { + "kind": "internal" + } + }, + { + "args": { + "type": "object", + "value": {} + }, + "functionType": "Mutation", + "identifier": "maintenance.js:backfillStrategyAgentSummaries", + "returns": { + "type": "number" + }, + "visibility": { + "kind": "internal" + } + }, + { + "args": { + "type": "object", + "value": { + "paginationOpts": { + "fieldType": { + "type": "object", + "value": { + "cursor": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "endCursor": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "optional": true + }, + "id": { + "fieldType": { + "type": "number" + }, + "optional": true + }, + "maximumBytesRead": { + "fieldType": { + "type": "number" + }, + "optional": true + }, + "maximumRowsRead": { + "fieldType": { + "type": "number" + }, + "optional": true + }, + "numItems": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + "optional": false + }, + "table": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "literal", + "value": "elements" + }, + { + "type": "literal", + "value": "lineups" + } + ] + }, + "optional": false + } + } + }, + "functionType": "Query", + "identifier": "maintenance.js:checkAssetReferences", + "returns": null, + "visibility": { + "kind": "internal" + } + }, + { + "args": { + "type": "object", + "value": { + "paginationOpts": { + "fieldType": { + "type": "object", + "value": { + "cursor": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "endCursor": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "optional": true + }, + "id": { + "fieldType": { + "type": "number" + }, + "optional": true + }, + "maximumBytesRead": { + "fieldType": { + "type": "number" + }, + "optional": true + }, + "maximumRowsRead": { + "fieldType": { + "type": "number" + }, + "optional": true + }, + "numItems": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + "optional": false + }, + "table": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "literal", + "value": "elements" + }, + { + "type": "literal", + "value": "lineups" + } + ] + }, + "optional": false + } + } + }, + "functionType": "Query", + "identifier": "maintenance.js:findShownUnavailableAssets", + "returns": null, + "visibility": { + "kind": "internal" + } + }, + { + "args": { + "type": "object", + "value": { + "pageId": { + "fieldType": { + "tableName": "pages", + "type": "id" + }, + "optional": false + }, + "strategyId": { + "fieldType": { + "tableName": "strategies", + "type": "id" + }, + "optional": false + } + } + }, + "functionType": "Mutation", + "identifier": "maintenance.js:purgeDeletedPageOrphans", + "returns": null, + "visibility": { + "kind": "internal" + } + }, + { + "args": { + "type": "object", + "value": {} + }, + "functionType": "Mutation", + "identifier": "maintenance.js:purgeOldOperationEvents", + "returns": null, + "visibility": { + "kind": "internal" + } + }, + { + "args": { + "type": "object", + "value": {} + }, + "functionType": "Mutation", + "identifier": "maintenance.js:purgeOldTombstones", + "returns": null, + "visibility": { + "kind": "internal" + } + }, + { + "args": { + "type": "object", + "value": {} + }, + "functionType": "Mutation", + "identifier": "maintenance.js:purgeTrashedPages", + "returns": null, + "visibility": { + "kind": "internal" + } + }, + { + "args": { + "type": "object", + "value": { + "accountSubject": { + "fieldType": { + "type": "string" + }, + "optional": true + }, + "checkTrashedPageDeletes": { + "fieldType": { + "type": "boolean" + }, + "optional": true + }, + "clientId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "clientProtocolVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "ops": { + "fieldType": { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "object", + "value": { + "expectedStrategyRevision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "opId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "payload": { + "fieldType": { + "type": "object", + "value": { + "clearThemeOverridePalette": { + "fieldType": { + "type": "boolean" + }, + "optional": true + }, + "clearThemeProfileId": { + "fieldType": { + "type": "boolean" + }, + "optional": true + }, + "mapData": { + "fieldType": { + "type": "string" + }, + "optional": true + }, + "name": { + "fieldType": { + "type": "string" + }, + "optional": true + }, + "themeOverridePalette": { + "fieldType": { + "type": "object", + "value": { + "base": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "detail": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "highlight": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "optional": true + }, + "themeProfileId": { + "fieldType": { + "type": "string" + }, + "optional": true + } + } + }, + "optional": false + }, + "type": { + "fieldType": { + "type": "literal", + "value": "strategy.patch" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "expectedStrategyRevision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "opId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "pagePublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "payload": { + "fieldType": { + "type": "object", + "value": { + "isAttack": { + "fieldType": { + "type": "boolean" + }, + "optional": true + }, + "isAutoNamed": { + "fieldType": { + "type": "boolean" + }, + "optional": true + }, + "name": { + "fieldType": { + "type": "string" + }, + "optional": true + }, + "settings": { + "fieldType": { + "type": "object", + "value": { + "abilitySize": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "agentSize": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "useNeutralTeamColors": { + "fieldType": { + "type": "boolean" + }, + "optional": false + } + } + }, + "optional": true + } + } + }, + "optional": false + }, + "sortIndex": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "type": { + "fieldType": { + "type": "literal", + "value": "page.add" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "expectedPageRevision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "opId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "pagePublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "payload": { + "fieldType": { + "type": "object", + "value": { + "isAttack": { + "fieldType": { + "type": "boolean" + }, + "optional": true + }, + "isAutoNamed": { + "fieldType": { + "type": "boolean" + }, + "optional": true + }, + "name": { + "fieldType": { + "type": "string" + }, + "optional": true + }, + "settings": { + "fieldType": { + "type": "object", + "value": { + "abilitySize": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "agentSize": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "useNeutralTeamColors": { + "fieldType": { + "type": "boolean" + }, + "optional": false + } + } + }, + "optional": true + } + } + }, + "optional": false + }, + "type": { + "fieldType": { + "type": "literal", + "value": "page.patch" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "expectedStrategyRevision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "opId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "pagePublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "type": { + "fieldType": { + "type": "literal", + "value": "page.delete" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "expectedStrategyRevision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "opId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "pagePublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "sortIndex": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "type": { + "fieldType": { + "type": "literal", + "value": "page.reorder" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "expectedPageContentRevision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "opId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "pagePublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "settings": { + "fieldType": { + "type": "object", + "value": { + "abilitySize": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "agentSize": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "useNeutralTeamColors": { + "fieldType": { + "type": "boolean" + }, + "optional": false + } + } + }, + "optional": false + }, + "type": { + "fieldType": { + "type": "literal", + "value": "pageContent.patch" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "elementPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "expectedElementRevision": { + "fieldType": { + "type": "number" + }, + "optional": true + }, + "opId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "pagePublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "payload": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "object", + "value": { + "data": { + "fieldType": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { + "type": "array", + "value": { "type": "union", "value": [ { @@ -47100,136 +49006,33 @@ "type": "string" } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -47244,16 +49047,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -47295,16 +49149,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -47319,16 +49224,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -47397,16 +49353,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -47421,16 +49428,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -47472,16 +49530,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -47496,16 +49605,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -47601,16 +49761,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -47625,16 +49836,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -47676,16 +49938,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -47700,21 +50013,72 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - "optional": false - } + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] }, @@ -47778,16 +50142,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -47802,16 +50217,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -47853,16 +50319,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -47877,16 +50394,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -47909,114 +50477,15 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "lineupLanding" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + } }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -48086,16 +50555,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] } @@ -48110,16 +50732,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] }, @@ -48161,118 +50936,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { + "keys": { "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } ] } @@ -48287,16 +51113,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] }, @@ -48304,122 +51283,20 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -48467,16 +51344,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] } @@ -48491,16 +51521,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] }, @@ -48542,16 +51725,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] } @@ -48566,16 +51902,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] }, @@ -48588,605 +52077,92 @@ } } ] - } + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + }, + "optional": false + }, + "kind": { + "fieldType": { + "type": "literal", + "value": "agent" + }, + "optional": false + }, + "payloadVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "data": { + "fieldType": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "boolean" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } + "type": "number" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -49256,16 +52232,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -49280,16 +52307,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -49331,16 +52409,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -49355,16 +52484,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -49433,16 +52613,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -49457,16 +52688,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -49508,16 +52790,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -49532,21 +52865,72 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] }, @@ -49559,649 +52943,37 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "null" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "boolean" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "number" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -50249,16 +53021,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -50273,16 +53096,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -50324,16 +53198,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -50348,16 +53273,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -50370,343 +53346,37 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "null" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "boolean" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "number" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -50732,16 +53402,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -50756,16 +53477,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -50773,25 +53545,229 @@ } } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } - } - ] - }, - "optional": false + ] + }, + "optional": false + } } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -50861,16 +53837,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -50885,16 +53912,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -50936,16 +54014,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -50960,16 +54089,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -51038,16 +54218,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -51062,16 +54293,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -51113,16 +54395,67 @@ "type": "union", "value": [ { - "type": "null" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "boolean" - }, - { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -51137,16 +54470,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -51164,397 +54548,6 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "lineupLink" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - } - ] - }, - "optional": false - }, - "publicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "revision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "sortIndex": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "strategyPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "updatedAt": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - } - }, - "visibility": { - "kind": "public" - } - }, - { - "args": { - "type": "object", - "value": { - "clientProtocolVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "strategyPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - } - } - }, - "functionType": "Query", - "identifier": "lineups.js:listForStrategy", - "returns": { - "type": "array", - "value": { - "type": "object", - "value": { - "createdAt": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "deleted": { - "fieldType": { - "type": "boolean" - }, - "optional": false - }, - "pagePublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "payload": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] } }, { @@ -51611,16 +54604,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] } @@ -51635,16 +54781,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] }, @@ -51686,16 +54985,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] } @@ -51710,24 +55162,177 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, "optional": false } } @@ -51737,214 +55342,109 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + "optional": false + } + } + ] + }, + "optional": false + } + }, + "optional": false + }, + "kind": { + "fieldType": { + "type": "literal", + "value": "ability" + }, + "optional": false + }, + "payloadVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "data": { + "fieldType": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -51992,16 +55492,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -52016,16 +55567,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -52067,16 +55669,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -52091,16 +55744,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -52113,343 +55817,37 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "null" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "boolean" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "number" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -52475,16 +55873,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -52499,16 +55948,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -52516,166 +56016,18 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { "type": "union", "value": [ { @@ -52691,49 +56043,197 @@ "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } - } - ] - }, - "optional": false + ] + }, + "optional": false + } } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -52781,16 +56281,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -52805,16 +56356,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -52856,16 +56458,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -52880,16 +56533,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -52902,118 +56606,15 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + } }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -53039,16 +56640,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] } @@ -53063,255 +56817,6 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { "type": "union", "value": [ { @@ -53328,228 +56833,223 @@ } ] }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -53597,16 +57097,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -53621,16 +57172,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -53672,16 +57274,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -53696,16 +57349,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -53718,343 +57422,37 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { + "type": "array", + "value": { "type": "union", "value": [ { @@ -54080,16 +57478,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -54104,16 +57553,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -54121,166 +57621,18 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { "type": "union", "value": [ { @@ -54296,49 +57648,197 @@ "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } - } - ] - }, - "optional": false + ] + }, + "optional": false + } } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -54386,16 +57886,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -54410,16 +57961,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -54461,16 +58063,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -54485,16 +58138,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -54507,416 +58211,11 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "lineupOrigin" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" + } + }, + { + "keys": { + "type": "string" }, "type": "record", "values": { @@ -54946,16 +58245,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] } @@ -54970,16 +58422,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] }, @@ -54997,134 +58602,131 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + "optional": false + } + } + ] + }, + "optional": false + } + }, + "optional": false + }, + "kind": { + "fieldType": { + "type": "literal", + "value": "drawing" + }, + "optional": false + }, + "payloadVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "data": { + "fieldType": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -55150,16 +58752,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -55174,526 +58827,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { + "keys": { "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } ] }, @@ -55735,220 +58929,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { + "keys": { "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } ] } @@ -55963,16 +59004,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -56041,16 +59133,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -56065,16 +59208,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -56116,40 +59310,142 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" + "keys": { + "type": "string" }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -56167,649 +59463,37 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "null" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "boolean" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "number" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -56857,16 +59541,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -56881,16 +59616,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -56932,16 +59718,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -56956,16 +59793,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -56978,343 +59866,37 @@ } } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "null" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "boolean" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "number" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -57340,16 +59922,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -57364,16 +59997,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -57381,25 +60065,229 @@ } } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } - } - ] - }, - "optional": false + ] + }, + "optional": false + } } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -57469,16 +60357,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -57493,16 +60432,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -57544,16 +60534,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -57568,16 +60609,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -57646,16 +60738,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -57670,26 +60813,77 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { "keys": { "type": "string" }, @@ -57721,16 +60915,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -57745,16 +60990,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -57772,309 +61068,6 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "lineupLanding" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] } }, { @@ -58131,16 +61124,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] } @@ -58155,16 +61301,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] }, @@ -58206,16 +61505,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] } @@ -58230,220 +61682,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] }, @@ -58456,15 +61857,114 @@ } } ] - } + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + }, + "optional": false + }, + "kind": { + "fieldType": { + "type": "literal", + "value": "text" + }, + "optional": false + }, + "payloadVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "data": { + "fieldType": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, { - "keys": { - "type": "string" + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -58512,16 +62012,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -58536,16 +62087,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -58587,16 +62189,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -58611,16 +62264,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -58633,137 +62337,40 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { "type": "union", "value": [ { @@ -58780,65 +62387,167 @@ } ] }, - "optional": false - } + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { "type": "union", "value": [ { @@ -58855,405 +62564,196 @@ } ] }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } - } - ] - }, - "optional": false + ] + }, + "optional": false + } } - } - ] - }, - "optional": false + ] + }, + "optional": false + } } - } - ] - } - }, - { - "keys": { - "type": "string" + ] + } }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -59301,16 +62801,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -59325,16 +62876,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -59376,16 +62978,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -59400,16 +63053,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -59422,118 +63126,15 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + } }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -59559,102 +63160,6 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { "type": "union", "value": [ { @@ -59671,167 +63176,167 @@ } ] }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { + "keys": { "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] }, - { - "type": "string" - } - ] - }, - "optional": false - } + "optional": false + } + } + ] } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { "type": "union", "value": [ { @@ -59848,228 +63353,223 @@ } ] }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -60117,16 +63617,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -60141,16 +63692,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -60192,16 +63794,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -60216,16 +63869,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -60238,343 +63942,37 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "null" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "boolean" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "number" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -60600,16 +63998,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -60624,16 +64073,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -60641,166 +64141,18 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { "type": "union", "value": [ { @@ -60816,49 +64168,197 @@ "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } - } - ] - }, - "optional": false + ] + }, + "optional": false + } } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -60906,16 +64406,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -60930,16 +64481,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -60981,16 +64583,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -61005,16 +64658,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -61027,988 +64731,15 @@ } } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "lineupLink" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - } - ] - }, - "optional": false - }, - "publicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "revision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "sortIndex": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "strategyPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "updatedAt": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - } - }, - "visibility": { - "kind": "public" - } - }, - { - "args": { - "type": "object", - "value": { - "paginationOpts": { - "fieldType": { - "type": "object", - "value": { - "cursor": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "optional": false - }, - "endCursor": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "optional": true - }, - "id": { - "fieldType": { - "type": "number" - }, - "optional": true - }, - "maximumBytesRead": { - "fieldType": { - "type": "number" - }, - "optional": true - }, - "maximumRowsRead": { - "fieldType": { - "type": "number" - }, - "optional": true - }, - "numItems": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - "optional": false - }, - "table": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "literal", - "value": "elements" - }, - { - "type": "literal", - "value": "lineups" - } - ] - }, - "optional": false - } - } - }, - "functionType": "Mutation", - "identifier": "maintenance.js:backfillAssetReferences", - "returns": null, - "visibility": { - "kind": "internal" - } - }, - { - "args": { - "type": "object", - "value": {} - }, - "functionType": "Mutation", - "identifier": "maintenance.js:backfillStrategyAgentSummaries", - "returns": { - "type": "number" - }, - "visibility": { - "kind": "internal" - } - }, - { - "args": { - "type": "object", - "value": { - "paginationOpts": { - "fieldType": { - "type": "object", - "value": { - "cursor": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "optional": false - }, - "endCursor": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "optional": true - }, - "id": { - "fieldType": { - "type": "number" - }, - "optional": true - }, - "maximumBytesRead": { - "fieldType": { - "type": "number" - }, - "optional": true - }, - "maximumRowsRead": { - "fieldType": { - "type": "number" - }, - "optional": true - }, - "numItems": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - "optional": false - }, - "table": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "literal", - "value": "elements" - }, - { - "type": "literal", - "value": "lineups" - } - ] - }, - "optional": false - } - } - }, - "functionType": "Query", - "identifier": "maintenance.js:checkAssetReferences", - "returns": null, - "visibility": { - "kind": "internal" - } - }, - { - "args": { - "type": "object", - "value": { - "paginationOpts": { - "fieldType": { - "type": "object", - "value": { - "cursor": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "optional": false - }, - "endCursor": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "optional": true - }, - "id": { - "fieldType": { - "type": "number" - }, - "optional": true - }, - "maximumBytesRead": { - "fieldType": { - "type": "number" - }, - "optional": true - }, - "maximumRowsRead": { - "fieldType": { - "type": "number" - }, - "optional": true - }, - "numItems": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - "optional": false - }, - "table": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "literal", - "value": "elements" - }, - { - "type": "literal", - "value": "lineups" - } - ] - }, - "optional": false - } - } - }, - "functionType": "Query", - "identifier": "maintenance.js:findShownUnavailableAssets", - "returns": null, - "visibility": { - "kind": "internal" - } - }, - { - "args": { - "type": "object", - "value": { - "pageId": { - "fieldType": { - "tableName": "pages", - "type": "id" - }, - "optional": false - }, - "strategyId": { - "fieldType": { - "tableName": "strategies", - "type": "id" - }, - "optional": false - } - } - }, - "functionType": "Mutation", - "identifier": "maintenance.js:purgeDeletedPageOrphans", - "returns": null, - "visibility": { - "kind": "internal" - } - }, - { - "args": { - "type": "object", - "value": {} - }, - "functionType": "Mutation", - "identifier": "maintenance.js:purgeOldOperationEvents", - "returns": null, - "visibility": { - "kind": "internal" - } - }, - { - "args": { - "type": "object", - "value": {} - }, - "functionType": "Mutation", - "identifier": "maintenance.js:purgeOldTombstones", - "returns": null, - "visibility": { - "kind": "internal" - } - }, - { - "args": { - "type": "object", - "value": {} - }, - "functionType": "Mutation", - "identifier": "maintenance.js:purgeTrashedPages", - "returns": null, - "visibility": { - "kind": "internal" - } - }, - { - "args": { - "type": "object", - "value": { - "accountSubject": { - "fieldType": { - "type": "string" - }, - "optional": true - }, - "checkLineupEndDeletes": { - "fieldType": { - "type": "boolean" - }, - "optional": true - }, - "checkLineupLinkEnds": { - "fieldType": { - "type": "boolean" - }, - "optional": true - }, - "checkTrashedPageDeletes": { - "fieldType": { - "type": "boolean" - }, - "optional": true - }, - "clientId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "clientProtocolVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "ops": { - "fieldType": { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "object", - "value": { - "expectedStrategyRevision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "opId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "payload": { - "fieldType": { - "type": "object", - "value": { - "clearThemeOverridePalette": { - "fieldType": { - "type": "boolean" - }, - "optional": true - }, - "clearThemeProfileId": { - "fieldType": { - "type": "boolean" - }, - "optional": true - }, - "mapData": { - "fieldType": { - "type": "string" - }, - "optional": true - }, - "name": { - "fieldType": { - "type": "string" - }, - "optional": true - }, - "themeOverridePalette": { - "fieldType": { - "type": "object", - "value": { - "base": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "detail": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "highlight": { - "fieldType": { - "type": "string" - }, - "optional": false - } - } - }, - "optional": true - }, - "themeProfileId": { - "fieldType": { - "type": "string" - }, - "optional": true - } - } - }, - "optional": false - }, - "type": { - "fieldType": { - "type": "literal", - "value": "strategy.patch" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "expectedStrategyRevision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "opId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "pagePublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "payload": { - "fieldType": { - "type": "object", - "value": { - "isAttack": { - "fieldType": { - "type": "boolean" - }, - "optional": true - }, - "isAutoNamed": { - "fieldType": { - "type": "boolean" - }, - "optional": true - }, - "name": { - "fieldType": { - "type": "string" - }, - "optional": true - }, - "settings": { - "fieldType": { - "type": "object", - "value": { - "abilitySize": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "agentSize": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "useNeutralTeamColors": { - "fieldType": { - "type": "boolean" - }, - "optional": false - } - } - }, - "optional": true - } - } - }, - "optional": false - }, - "sortIndex": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "type": { - "fieldType": { - "type": "literal", - "value": "page.add" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "expectedPageRevision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "opId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "pagePublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "payload": { - "fieldType": { - "type": "object", - "value": { - "isAttack": { - "fieldType": { - "type": "boolean" - }, - "optional": true - }, - "isAutoNamed": { - "fieldType": { - "type": "boolean" - }, - "optional": true - }, - "name": { - "fieldType": { - "type": "string" - }, - "optional": true - }, - "settings": { - "fieldType": { - "type": "object", - "value": { - "abilitySize": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "agentSize": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "useNeutralTeamColors": { - "fieldType": { - "type": "boolean" - }, - "optional": false - } - } - }, - "optional": true - } - } - }, - "optional": false - }, - "type": { - "fieldType": { - "type": "literal", - "value": "page.patch" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "expectedStrategyRevision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "opId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "pagePublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "type": { - "fieldType": { - "type": "literal", - "value": "page.delete" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "expectedStrategyRevision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "opId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "pagePublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "sortIndex": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "type": { - "fieldType": { - "type": "literal", - "value": "page.reorder" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "expectedPageContentRevision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "opId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "pagePublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "settings": { - "fieldType": { - "type": "object", - "value": { - "abilitySize": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "agentSize": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "useNeutralTeamColors": { - "fieldType": { - "type": "boolean" - }, - "optional": false - } - } - }, - "optional": false - }, - "type": { - "fieldType": { - "type": "literal", - "value": "pageContent.patch" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "elementPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "expectedElementRevision": { - "fieldType": { - "type": "number" - }, - "optional": true - }, - "opId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "pagePublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "payload": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -62056,16 +64787,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -62080,8 +64862,464 @@ "type": "union", "value": [ { - "type": "null" - }, + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + }, + "optional": false + }, + "kind": { + "fieldType": { + "type": "literal", + "value": "image" + }, + "optional": false + }, + "payloadVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "data": { + "fieldType": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, { "type": "boolean" }, @@ -65158,7 +68396,7 @@ "kind": { "fieldType": { "type": "literal", - "value": "agent" + "value": "utility" }, "optional": false }, @@ -65169,7 +68407,57 @@ "optional": false } } - }, + } + ] + }, + "optional": false + }, + "sortIndex": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "type": { + "fieldType": { + "type": "literal", + "value": "element.add" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "elementPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "expectedElementRevision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "opId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "pagePublicId": { + "fieldType": { + "type": "string" + }, + "optional": true + }, + "payload": { + "fieldType": { + "type": "union", + "value": [ { "type": "object", "value": { @@ -68418,7 +71706,7 @@ "kind": { "fieldType": { "type": "literal", - "value": "ability" + "value": "agent" }, "optional": false }, @@ -71678,7 +74966,7 @@ "kind": { "fieldType": { "type": "literal", - "value": "drawing" + "value": "ability" }, "optional": false }, @@ -74938,7 +78226,7 @@ "kind": { "fieldType": { "type": "literal", - "value": "text" + "value": "drawing" }, "optional": false }, @@ -78198,7 +81486,7 @@ "kind": { "fieldType": { "type": "literal", - "value": "image" + "value": "text" }, "optional": false }, @@ -81458,7 +84746,7 @@ "kind": { "fieldType": { "type": "literal", - "value": "utility" + "value": "image" }, "optional": false }, @@ -81469,57 +84757,7 @@ "optional": false } } - } - ] - }, - "optional": false - }, - "sortIndex": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "type": { - "fieldType": { - "type": "literal", - "value": "element.add" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "elementPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "expectedElementRevision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "opId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "pagePublicId": { - "fieldType": { - "type": "string" - }, - "optional": true - }, - "payload": { - "fieldType": { - "type": "union", - "value": [ + }, { "type": "object", "value": { @@ -84768,7 +88006,7 @@ "kind": { "fieldType": { "type": "literal", - "value": "agent" + "value": "utility" }, "optional": false }, @@ -84779,283 +88017,253 @@ "optional": false } } - }, - { - "type": "object", - "value": { - "data": { + } + ] + }, + "optional": true + }, + "sortIndex": { + "fieldType": { + "type": "number" + }, + "optional": true + }, + "type": { + "fieldType": { + "type": "literal", + "value": "element.patch" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "elementPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "expectedElementRevision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "opId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "pagePublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "type": { + "fieldType": { + "type": "literal", + "value": "element.delete" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "elementPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "expectedElementRevision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "opId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "pagePublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "sortIndex": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "type": { + "fieldType": { + "type": "literal", + "value": "element.reorder" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "expectedLineupRevision": { + "fieldType": { + "type": "number" + }, + "optional": true + }, + "lineupPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "opId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "pagePublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "payload": { + "fieldType": { + "type": "object", + "value": { + "data": { + "fieldType": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { "fieldType": { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -85081,271 +88289,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "null" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "boolean" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "number" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "string" } ] } @@ -85360,67 +88313,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -85462,67 +88364,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -85537,67 +88388,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -85610,241 +88410,37 @@ } } ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + } }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -85870,67 +88466,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -85945,67 +88490,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -86013,20 +88507,122 @@ } } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -86074,67 +88670,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -86149,67 +88694,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -86251,67 +88745,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -86326,67 +88769,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -86399,113 +88791,112 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -86572,277 +88963,74 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false } - ] - } + } + ] }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] + "optional": false + } } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, { - "keys": { - "type": "string" + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -86890,67 +89078,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -86965,67 +89102,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -87067,67 +89153,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -87142,67 +89177,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -87215,430 +89199,22 @@ } } ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + } }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" + "type": "null" }, { "type": "boolean" @@ -87679,67 +89255,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -87754,67 +89279,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -87856,67 +89330,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -87931,67 +89354,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -88009,114 +89381,15 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "ability" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + } }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -88489,62 +89762,159 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -88561,9 +89931,65 @@ } ] }, - { - "type": "array", - "value": { + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -88579,66 +90005,168 @@ "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "optional": false } - ] - } + } + ] }, - { - "keys": { - "type": "string" + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { "type": "string" } ] }, - { - "type": "array", - "value": { + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -88654,71 +90182,326 @@ "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "optional": false } - ] - }, - "optional": false - } + } + ] + }, + "optional": false } - ] - } + } + ] }, - { - "keys": { - "type": "string" + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "boolean" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "number" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -88785,18 +90568,217 @@ } } ] - } + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, { - "keys": { - "type": "string" + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -88813,9 +90795,167 @@ } ] }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -88831,81 +90971,30 @@ "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "optional": false } - ] - }, - "optional": false - } + } + ] + }, + "optional": false } - ] - }, - "optional": false - } + } + ] + }, + "optional": false } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -89278,406 +91367,142 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false } - ] - }, - "optional": false - } + } + ] + }, + "optional": false } - ] - } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + }, + "optional": false + }, + "kind": { + "fieldType": { + "type": "literal", + "value": "lineup" + }, + "optional": false + }, + "payloadVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + "optional": false + }, + "sortIndex": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "type": { + "fieldType": { + "type": "literal", + "value": "lineup.add" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "expectedLineupRevision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "lineupPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "opId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "pagePublicId": { + "fieldType": { + "type": "string" + }, + "optional": true + }, + "payload": { + "fieldType": { + "type": "object", + "value": { + "data": { + "fieldType": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, { - "keys": { - "type": "string" + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -89769,67 +91594,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -89844,67 +91618,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -89946,67 +91669,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -90021,67 +91693,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -90150,67 +91771,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -90225,67 +91795,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -90327,67 +91846,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -90402,67 +91870,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -90558,67 +91975,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -90633,67 +91999,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -90735,67 +92050,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -90810,67 +92074,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -90939,67 +92152,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -91014,67 +92176,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -91116,67 +92227,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -91191,67 +92251,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -91274,131 +92283,236 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "drawing" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -91567,37 +92681,139 @@ } } ] - } + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -91664,122 +92880,20 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -91948,101 +93062,153 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] } @@ -92057,67 +93223,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -92125,91 +93240,192 @@ } } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } - } - ] + ] + } }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { + { + "keys": { "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -92276,18 +93492,115 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -92304,92 +93617,41 @@ } ] }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false + } + } + ] } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + "type": "array", + "value": { "type": "union", "value": [ { @@ -92405,10 +93667,15 @@ "type": "string" } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -92424,47 +93691,93 @@ "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "optional": false } - ] - } + } + ] }, - { - "keys": { - "type": "string" + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -92481,116 +93794,19 @@ } ] }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false + } + } + ] } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" }, { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -92657,95 +93873,96 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false } - ] - } - }, + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ { - "keys": { - "type": "string" + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -92771,6 +93988,102 @@ "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -92787,65 +94100,167 @@ } ] }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "null" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -92862,126 +94277,29 @@ } ] }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -93354,15 +94672,487 @@ } } ] - } + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + }, + "optional": false + }, + "kind": { + "fieldType": { + "type": "literal", + "value": "lineup" + }, + "optional": false + }, + "payloadVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + "optional": true + }, + "sortIndex": { + "fieldType": { + "type": "number" + }, + "optional": true + }, + "type": { + "fieldType": { + "type": "literal", + "value": "lineup.patch" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "expectedLineupRevision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "lineupPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "opId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "pagePublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "type": { + "fieldType": { + "type": "literal", + "value": "lineup.delete" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "expectedLineupRevision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "lineupPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "opId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "pagePublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "sortIndex": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "type": { + "fieldType": { + "type": "literal", + "value": "lineup.reorder" + }, + "optional": false + } + } + } + ] + } + }, + "optional": false + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Mutation", + "identifier": "ops.js:applyBatch", + "returns": { + "type": "object", + "value": { + "results": { + "fieldType": { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "object", + "value": { + "appliedRevision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "opId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "status": { + "fieldType": { + "type": "literal", + "value": "applied" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "currentRevision": { + "fieldType": { + "type": "number" + }, + "optional": true + }, + "opId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "status": { + "fieldType": { + "type": "literal", + "value": "noop" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "current": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "object", + "value": { + "revision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "type": { + "fieldType": { + "type": "literal", + "value": "strategy" + }, + "optional": false + }, + "value": { + "fieldType": { + "type": "object", + "value": { + "mapData": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "name": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "themeOverridePalette": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "object", + "value": { + "base": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "detail": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "highlight": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "themeProfileId": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "optional": false + } + } + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "revision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "type": { + "fieldType": { + "type": "literal", + "value": "page" + }, + "optional": false + }, + "value": { + "fieldType": { + "type": "object", + "value": { + "isAttack": { + "fieldType": { + "type": "boolean" + }, + "optional": false + }, + "isAutoNamed": { + "fieldType": { + "type": "boolean" + }, + "optional": true + }, + "name": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "sortIndex": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "revision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "type": { + "fieldType": { + "type": "literal", + "value": "pageContent" + }, + "optional": false + }, + "value": { + "fieldType": { + "type": "object", + "value": { + "settings": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "object", + "value": { + "abilitySize": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "agentSize": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "useNeutralTeamColors": { + "fieldType": { + "type": "boolean" + }, + "optional": false + } + } + }, + { + "type": "null" + } + ] + }, + "optional": false + } + } + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "revision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "type": { + "fieldType": { + "type": "literal", + "value": "element" + }, + "optional": false + }, + "value": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "object", + "value": { + "data": { + "fieldType": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, { - "keys": { - "type": "string" + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -93432,16 +95222,67 @@ "type": "union", "value": [ { - "type": "null" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "boolean" - }, - { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -93456,16 +95297,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -93507,16 +95399,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -93531,16 +95474,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -93609,102 +95603,6 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { "type": "union", "value": [ { @@ -93721,312 +95619,51 @@ } ] }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { + "keys": { "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } ] } @@ -94041,16 +95678,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -94092,16 +95780,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -94116,16 +95855,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -94221,16 +96011,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -94245,16 +96086,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -94296,16 +96188,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -94320,16 +96263,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -94398,16 +96392,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -94422,16 +96467,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -94473,16 +96569,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -94497,16 +96644,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -94529,114 +96727,15 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "text" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + } }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -94706,16 +96805,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] } @@ -94730,16 +96982,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] }, @@ -94781,16 +97186,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] } @@ -94805,118 +97363,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] }, @@ -94924,122 +97533,20 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -95087,16 +97594,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] } @@ -95111,16 +97771,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] }, @@ -95162,16 +97975,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] } @@ -95186,16 +98152,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] }, @@ -95208,605 +98327,92 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false } - ] - }, - "optional": false - } + } + ] + }, + "optional": false } - ] - } + } + ] }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { + "optional": false + } + }, + "optional": false + }, + "kind": { + "fieldType": { + "type": "literal", + "value": "agent" + }, + "optional": false + }, + "payloadVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "data": { + "fieldType": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -95876,16 +98482,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -95900,16 +98557,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -95951,16 +98659,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -95975,16 +98734,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -96053,16 +98863,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -96077,16 +98938,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -96128,40 +99040,142 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" + "keys": { + "type": "string" }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -96179,649 +99193,37 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "null" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "boolean" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "number" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -96869,16 +99271,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -96893,16 +99346,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -96944,16 +99448,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -96968,16 +99523,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -96990,343 +99596,37 @@ } } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "null" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "boolean" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "number" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -97352,16 +99652,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -97376,16 +99727,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -97393,25 +99795,229 @@ } } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } - } - ] - }, - "optional": false + ] + }, + "optional": false + } } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -97481,16 +100087,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -97505,16 +100162,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -97556,16 +100264,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -97580,16 +100339,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -97658,16 +100468,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -97682,28 +100543,79 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" }, "type": "record", "values": { @@ -97733,16 +100645,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -97757,16 +100720,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -97784,309 +100798,6 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "image" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] } }, { @@ -98143,16 +100854,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] } @@ -98167,16 +101031,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] }, @@ -98218,16 +101235,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] } @@ -98242,220 +101412,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] }, @@ -98468,15 +101587,114 @@ } } ] - } + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + }, + "optional": false + }, + "kind": { + "fieldType": { + "type": "literal", + "value": "ability" + }, + "optional": false + }, + "payloadVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "data": { + "fieldType": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, { - "keys": { - "type": "string" + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -98524,16 +101742,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -98548,16 +101817,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -98599,16 +101919,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -98623,16 +101994,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -98645,343 +102067,37 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "null" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "boolean" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "number" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -99007,16 +102123,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -99031,16 +102198,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -99048,166 +102266,18 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { "type": "union", "value": [ { @@ -99223,49 +102293,197 @@ "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } - } - ] - }, - "optional": false + ] + }, + "optional": false + } } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -99313,16 +102531,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -99337,16 +102606,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -99388,16 +102708,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -99412,16 +102783,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -99434,118 +102856,15 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + } }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -99571,16 +102890,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] } @@ -99595,16 +103067,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] }, @@ -99612,476 +103237,69 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false } - ] - } + } + ] }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] + "optional": false + } } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -100129,16 +103347,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -100153,16 +103422,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -100204,16 +103524,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -100228,16 +103599,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -100250,361 +103672,55 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { "type": "array", @@ -100612,16 +103728,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -100636,16 +103803,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -100653,166 +103871,18 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { "type": "union", "value": [ { @@ -100828,49 +103898,197 @@ "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } - } - ] - }, - "optional": false + ] + }, + "optional": false + } } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -100918,16 +104136,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -100942,16 +104211,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -100993,16 +104313,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -101017,16 +104388,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -101039,544 +104461,11 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "utility" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - } - ] - }, - "optional": true - }, - "sortIndex": { - "fieldType": { - "type": "number" - }, - "optional": true - }, - "type": { - "fieldType": { - "type": "literal", - "value": "element.patch" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "elementPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "expectedElementRevision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "opId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "pagePublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "type": { - "fieldType": { - "type": "literal", - "value": "element.delete" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "elementPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "expectedElementRevision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "opId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "pagePublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "sortIndex": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "type": { - "fieldType": { - "type": "literal", - "value": "element.reorder" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "expectedLineupRevision": { - "fieldType": { - "type": "number" - }, - "optional": true - }, - "lineupPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "opId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "pagePublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "payload": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" + } + }, + { + "keys": { + "type": "string" }, "type": "record", "values": { @@ -101606,16 +104495,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] } @@ -101630,16 +104672,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] }, @@ -101657,137 +104852,134 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + "optional": false + } + } + ] + }, + "optional": false + } + }, + "optional": false + }, + "kind": { + "fieldType": { + "type": "literal", + "value": "drawing" + }, + "optional": false + }, + "payloadVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "data": { + "fieldType": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { @@ -101810,118 +105002,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { + "keys": { "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } ] } @@ -101936,16 +105077,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -101987,16 +105179,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -102011,16 +105254,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -102033,343 +105327,37 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "null" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "boolean" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "number" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -102395,16 +105383,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -102419,16 +105458,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -102436,115 +105526,18 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { "type": "union", "value": [ { @@ -102561,99 +105554,196 @@ } ] }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } - } - ] - }, - "optional": false + ] + }, + "optional": false + } } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -102701,16 +105791,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -102725,16 +105866,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -102776,16 +105968,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -102800,16 +106043,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -102822,118 +106116,15 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + } }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -102959,16 +106150,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] } @@ -102983,16 +106327,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] }, @@ -103000,37 +106497,94 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { @@ -103046,487 +106600,74 @@ "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" }, { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { + "keys": { "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } ] } @@ -103541,16 +106682,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -103592,16 +106784,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -103616,16 +106859,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -103638,290 +106932,40 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { @@ -103937,79 +106981,74 @@ "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -104024,16 +107063,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -104041,166 +107131,18 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { "type": "union", "value": [ { @@ -104216,49 +107158,197 @@ "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } - } - ] - }, - "optional": false + ] + }, + "optional": false + } } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -104306,16 +107396,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -104330,16 +107471,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -104381,16 +107573,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -104405,16 +107648,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -104427,420 +107721,15 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "lineupOrigin" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -104866,16 +107755,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] } @@ -104890,16 +107932,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] }, @@ -104917,214 +108112,109 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + "optional": false + } + } + ] + }, + "optional": false + } + }, + "optional": false + }, + "kind": { + "fieldType": { + "type": "literal", + "value": "text" + }, + "optional": false + }, + "payloadVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "data": { + "fieldType": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -105172,16 +108262,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -105196,16 +108337,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -105247,16 +108439,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -105271,365 +108514,110 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] + "optional": false + } } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "null" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "boolean" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "number" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -105655,16 +108643,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -105679,16 +108718,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -105696,166 +108786,18 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { "type": "union", "value": [ { @@ -105871,49 +108813,197 @@ "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } - } - ] - }, - "optional": false + ] + }, + "optional": false + } } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -105961,16 +109051,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -105985,16 +109126,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -106036,18 +109228,69 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" - } - ] + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] } }, { @@ -106060,16 +109303,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -106082,118 +109376,15 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + } }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -106219,16 +109410,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] } @@ -106243,16 +109587,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] }, @@ -106260,192 +109757,91 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false } - ] - } + } + ] }, - { - "keys": { - "type": "string" + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -106471,16 +109867,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -106495,16 +109942,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -106512,115 +110010,18 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { "type": "union", "value": [ { @@ -106637,196 +110038,191 @@ } ] }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } - } - ] - }, - "optional": false + ] + }, + "optional": false + } } - } - ] - } - }, - { - "keys": { - "type": "string" + ] + } }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -106852,16 +110248,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -106876,16 +110323,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -106893,217 +110391,18 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + } }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "keys": { + "type": "string" }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { "type": "union", "value": [ { @@ -107120,121 +110419,218 @@ } ] }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -107260,16 +110656,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -107284,16 +110731,67 @@ "type": "union", "value": [ { - "type": "null" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "boolean" - }, - { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -107301,115 +110799,18 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { "type": "union", "value": [ { @@ -107426,41 +110827,194 @@ } ] }, - "optional": false - } - } - ] + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { @@ -107476,117 +111030,168 @@ "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } - } - ] + ] + } }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { + { + "keys": { "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { "type": "union", "value": [ { @@ -107603,19 +111208,317 @@ } ] }, - "optional": false - } - } - ] + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + }, + "optional": false + }, + "kind": { + "fieldType": { + "type": "literal", + "value": "image" + }, + "optional": false + }, + "payloadVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "data": { + "fieldType": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, { - "keys": { - "type": "string" + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -107682,151 +111585,663 @@ } } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } - } - ] + ] + } }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "lineupLanding" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -107874,16 +112289,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] } @@ -107898,16 +112466,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] }, @@ -107949,16 +112670,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] } @@ -107973,16 +112847,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] }, @@ -107995,83 +113022,89 @@ } } ] - } + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { "type": "union", "value": [ { @@ -108088,65 +113121,167 @@ } ] }, - "optional": false - } + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { "type": "union", "value": [ { @@ -108163,73 +113298,169 @@ } ] }, - "optional": false - } - } - ] - }, - "optional": false - } + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" }, { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -108255,102 +113486,6 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { "type": "union", "value": [ { @@ -108367,167 +113502,167 @@ } ] }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { + "keys": { "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] }, - { - "type": "string" - } - ] - }, - "optional": false - } + "optional": false + } + } + ] } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { "type": "union", "value": [ { @@ -108544,78 +113679,174 @@ } ] }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" }, { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -108663,16 +113894,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] } @@ -108687,16 +114071,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] }, @@ -108738,16 +114275,169 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] } @@ -108762,16 +114452,169 @@ "type": "union", "value": [ { - "type": "null" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "boolean" - }, - { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] }, @@ -108784,112 +114627,181 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + }, + "optional": false + }, + "kind": { + "fieldType": { + "type": "literal", + "value": "utility" + }, + "optional": false + }, + "payloadVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + } + ] + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "revision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "type": { + "fieldType": { + "type": "literal", + "value": "lineup" + }, + "optional": false + }, + "value": { + "fieldType": { + "type": "object", + "value": { + "data": { + "fieldType": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -108956,166 +114868,18 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { "type": "union", "value": [ { @@ -109131,15 +114895,10 @@ "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -109155,122 +114914,71 @@ "type": "string" } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } - } - ] - }, - "optional": false + ] + }, + "optional": false + } } - } - ] - } - }, - { - "keys": { - "type": "string" + ] + } }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -109337,101 +115045,100 @@ } } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } - } - ] - }, - "optional": false + ] + }, + "optional": false + } } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + ] + } }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -109479,16 +115186,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -109503,38 +115261,89 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { "type": "null" }, { @@ -109554,16 +115363,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -109578,16 +115438,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -109600,37 +115511,241 @@ } } ] - } + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -109656,16 +115771,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -109680,16 +115846,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -109697,122 +115914,20 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -109860,16 +115975,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -109884,16 +116050,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -109935,16 +116152,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -109959,16 +116227,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -109981,59 +116300,116 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { @@ -110049,15 +116425,10 @@ "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -110073,42 +116444,353 @@ "type": "string" } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } - } - ] - } - }, - { - "keys": { - "type": "string" + ] + } }, - "type": "record", - "values": { - "fieldType": { + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { @@ -110124,15 +116806,168 @@ "type": "string" } ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] }, - { - "keys": { - "type": "string" + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { @@ -110149,56 +116984,152 @@ } ] }, - "optional": false - } + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] } - ] - }, - "optional": false - } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false } - ] - }, - "optional": false - } + } + ] + }, + "optional": false } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -110268,16 +117199,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -110292,16 +117274,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -110343,16 +117376,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -110367,16 +117451,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -110445,220 +117580,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { + "keys": { "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" + "optional": false + } } ] } @@ -110673,16 +117655,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -110724,16 +117757,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -110748,16 +117832,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -110770,238 +117905,96 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false } - ] - }, - "optional": false - } + } + ] + }, + "optional": false } - ] - }, - "optional": false - } + } + ] + }, + "optional": false } - ] - }, - "optional": false - } + } + ] + }, + "optional": false } - ] + }, + "optional": false }, - "optional": false + "kind": { + "fieldType": { + "type": "literal", + "value": "lineup" + }, + "optional": false + }, + "payloadVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + } } }, "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "lineupLink" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false } } } ] }, + "optional": true + }, + "opId": { + "fieldType": { + "type": "string" + }, "optional": false }, - "sortIndex": { + "reason": { "fieldType": { - "type": "number" + "type": "union", + "value": [ + { + "type": "literal", + "value": "already_exists" + }, + { + "type": "literal", + "value": "element_strategy_mismatch" + }, + { + "type": "literal", + "value": "lineup_strategy_mismatch" + }, + { + "type": "literal", + "value": "missing_expected_revision" + }, + { + "type": "literal", + "value": "not_found" + }, + { + "type": "literal", + "value": "page_strategy_mismatch" + }, + { + "type": "literal", + "value": "revision_mismatch" + } + ] }, "optional": false }, - "type": { + "status": { "fieldType": { "type": "literal", - "value": "lineup.add" + "value": "rejected" }, "optional": false } @@ -111010,13 +118003,13 @@ { "type": "object", "value": { - "expectedLineupRevision": { + "code": { "fieldType": { - "type": "number" + "type": "string" }, "optional": false }, - "lineupPublicId": { + "message": { "fieldType": { "type": "string" }, @@ -111028,474 +118021,453 @@ }, "optional": false }, - "pagePublicId": { + "rawCode": { "fieldType": { "type": "string" }, - "optional": true + "optional": false }, - "payload": { + "status": { "fieldType": { - "type": "union", - "value": [ - { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } + "type": "literal", + "value": "failed" + }, + "optional": false + } + } + } + ] + } + }, + "optional": false + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "clientProtocolVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "pagePublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "shareToken": { + "fieldType": { + "type": "string" + }, + "optional": true + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Query", + "identifier": "page.js:getSnapshot", + "returns": { + "type": "object", + "value": { + "assets": { + "fieldType": { + "type": "array", + "value": { + "type": "object", + "value": { + "byteSize": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "fileExtension": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "height": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "legacyStoragePath": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "mimeType": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "provider": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "literal", + "value": "convex" + }, + { + "type": "literal", + "value": "r2" + } + ] + }, + "optional": false + }, + "publicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "uploadedAt": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "uploadStatus": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "literal", + "value": "pending" + }, + { + "type": "literal", + "value": "active" + }, + { + "type": "literal", + "value": "failed" + }, + { + "type": "literal", + "value": "deleted" + } + ] + }, + "optional": false + }, + "url": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "width": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "optional": false + } + } + } + }, + "optional": false + }, + "content": { + "fieldType": { + "type": "object", + "value": { + "createdAt": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "revision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "settings": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "object", + "value": { + "abilitySize": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "agentSize": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "useNeutralTeamColors": { + "fieldType": { + "type": "boolean" + }, + "optional": false + } + } + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "updatedAt": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + "optional": false + }, + "elements": { + "fieldType": { + "type": "array", + "value": { + "type": "object", + "value": { + "createdAt": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "deleted": { + "fieldType": { + "type": "boolean" + }, + "optional": false + }, + "elementType": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "literal", + "value": "agent" + }, + { + "type": "literal", + "value": "ability" + }, + { + "type": "literal", + "value": "drawing" + }, + { + "type": "literal", + "value": "text" + }, + { + "type": "literal", + "value": "image" + }, + { + "type": "literal", + "value": "utility" + } + ] + }, + "optional": false + }, + "pagePublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "payload": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "object", + "value": { + "data": { + "fieldType": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, { - "keys": { - "type": "string" + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -111543,67 +118515,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -111618,67 +118539,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -111720,67 +118590,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -111795,67 +118614,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -111868,89 +118636,83 @@ } } ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + } }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -111967,9 +118729,65 @@ } ] }, - { - "type": "array", - "value": { + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -111985,306 +118803,52 @@ "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "optional": false } - ] - } + } + ] }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] + "optional": false + } } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -112332,67 +118896,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -112407,67 +118920,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -112509,67 +118971,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -112584,67 +118995,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -112657,113 +119017,112 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -112830,95 +119189,96 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false } - ] - } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, { - "keys": { - "type": "string" + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -112944,67 +119304,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -113019,271 +119328,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "boolean" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "number" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "string" } ] }, @@ -113325,67 +119379,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -113400,67 +119403,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -113473,241 +119425,37 @@ } } ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + } }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -113733,67 +119481,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -113808,67 +119505,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -113876,20 +119522,122 @@ } } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -113937,67 +119685,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -114012,67 +119709,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -114114,67 +119760,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -114189,67 +119784,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -114262,119 +119806,632 @@ } } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } - } - ] - }, - "optional": false + ] + }, + "optional": false + } } - } - ] - }, - "optional": false + ] + }, + "optional": false + } } - } - ] + ] + } }, - "optional": false - } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "lineupOrigin" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -114747,37 +120804,241 @@ } } ] - } + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -114946,224 +121207,20 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -115536,406 +121593,97 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false } - ] - }, - "optional": false - } + } + ] + }, + "optional": false } - ] - } + } + ] }, + "optional": false + } + } + ] + }, + "optional": false + } + }, + "optional": false + }, + "kind": { + "fieldType": { + "type": "literal", + "value": "agent" + }, + "optional": false + }, + "payloadVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "data": { + "fieldType": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ { - "keys": { - "type": "string" + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -116027,67 +121775,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -116102,67 +121799,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -116204,67 +121850,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -116279,67 +121874,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -116408,67 +121952,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -116483,67 +121976,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -116585,67 +122027,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -116660,67 +122051,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -116816,67 +122156,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -116891,67 +122180,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -116993,67 +122231,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -117068,67 +122255,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -117197,67 +122333,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -117272,67 +122357,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -117374,67 +122408,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -117449,67 +122432,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -117532,131 +122464,236 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "lineupLanding" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -117825,37 +122862,139 @@ } } ] - } + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -117922,122 +123061,20 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -118206,15 +123243,118 @@ } } ] - } + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, { - "keys": { - "type": "string" + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -118240,6 +123380,102 @@ "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -118256,9 +123492,167 @@ } ] }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -118274,47 +123668,195 @@ "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "optional": false } - ] - } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, { - "keys": { - "type": "string" + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -118331,121 +123873,24 @@ } ] }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false + } + } + ] + }, + "optional": false + } + } + ] } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -118614,41 +124059,138 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", "value": [ { "type": "null" @@ -118664,9 +124206,65 @@ } ] }, - { - "type": "array", - "value": { + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -118682,47 +124280,93 @@ "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "optional": false } - ] - } + } + ] }, - { - "keys": { - "type": "string" + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -118739,116 +124383,19 @@ } ] }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false + } + } + ] } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" }, { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -118915,331 +124462,25 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false } - ] - } + } + ] }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false + } + } + ] } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -119612,6 +124853,309 @@ } } ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + }, + "optional": false + }, + "kind": { + "fieldType": { + "type": "literal", + "value": "ability" + }, + "optional": false + }, + "payloadVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "data": { + "fieldType": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] } }, { @@ -119668,67 +125212,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -119743,67 +125236,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -119845,67 +125287,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -119920,67 +125311,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -120076,6 +125416,102 @@ "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -120092,9 +125528,167 @@ } ] }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -120110,33 +125704,136 @@ "type": "string" } ] - } + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] } @@ -120151,67 +125848,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -120253,67 +125899,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -120328,67 +125923,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -120457,67 +126001,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -120532,67 +126025,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -120634,67 +126076,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -120709,67 +126100,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -120787,440 +126127,428 @@ } } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "lineupLink" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - } - ] - }, - "optional": true - }, - "sortIndex": { - "fieldType": { - "type": "number" - }, - "optional": true - }, - "type": { - "fieldType": { - "type": "literal", - "value": "lineup.patch" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "expectedLineupRevision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "lineupPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "opId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "pagePublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "type": { - "fieldType": { - "type": "literal", - "value": "lineup.delete" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "expectedLineupRevision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "lineupPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "opId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "pagePublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "sortIndex": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "type": { - "fieldType": { - "type": "literal", - "value": "lineup.reorder" - }, - "optional": false - } - } - } - ] - } - }, - "optional": false - }, - "strategyPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - } - } - }, - "functionType": "Mutation", - "identifier": "ops.js:applyBatch", - "returns": { - "type": "object", - "value": { - "results": { - "fieldType": { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "object", - "value": { - "appliedRevision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "opId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "status": { - "fieldType": { - "type": "literal", - "value": "applied" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "currentRevision": { - "fieldType": { - "type": "number" - }, - "optional": true - }, - "opId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "status": { - "fieldType": { - "type": "literal", - "value": "noop" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "current": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "object", - "value": { - "revision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "type": { - "fieldType": { - "type": "literal", - "value": "strategy" - }, - "optional": false - }, - "value": { - "fieldType": { - "type": "object", - "value": { - "mapData": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "name": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "themeOverridePalette": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "object", - "value": { - "base": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "detail": { - "fieldType": { - "type": "string" - }, - "optional": false + ] }, - "highlight": { - "fieldType": { - "type": "string" - }, - "optional": false - } + "optional": false } - }, - { - "type": "null" } ] - }, - "optional": false - }, - "themeProfileId": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "optional": false - } - } - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "revision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "type": { - "fieldType": { - "type": "literal", - "value": "page" - }, - "optional": false - }, - "value": { - "fieldType": { - "type": "object", - "value": { - "isAttack": { - "fieldType": { - "type": "boolean" - }, - "optional": false - }, - "isAutoNamed": { - "fieldType": { - "type": "boolean" - }, - "optional": true + } }, - "name": { - "fieldType": { + { + "keys": { "type": "string" }, - "optional": false - }, - "sortIndex": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "revision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "type": { - "fieldType": { - "type": "literal", - "value": "pageContent" - }, - "optional": false - }, - "value": { - "fieldType": { - "type": "object", - "value": { - "settings": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "object", - "value": { - "abilitySize": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "agentSize": { - "fieldType": { - "type": "number" + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, - "optional": false - }, - "useNeutralTeamColors": { - "fieldType": { + { "type": "boolean" }, - "optional": false - } - } - }, - { - "type": "null" - } - ] - }, - "optional": false - } - } - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "revision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "type": { - "fieldType": { - "type": "literal", - "value": "element" - }, - "optional": false - }, - "value": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" + { + "type": "number" + }, + { + "type": "string" + } + ] }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -121312,6 +126640,102 @@ "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -121328,351 +126752,51 @@ } ] }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ { "type": "null" }, @@ -121693,169 +126817,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -121870,169 +126841,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -122040,20 +126858,122 @@ } } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -122101,169 +127021,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -122278,169 +127045,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -122482,169 +127096,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -122659,169 +127120,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -122834,20 +127142,224 @@ } } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -122917,169 +127429,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -123094,169 +127453,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -123298,169 +127504,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -123475,169 +127528,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -123706,169 +127606,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -123883,169 +127630,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -124087,169 +127681,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -124264,169 +127705,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -124444,109 +127732,214 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "agent" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -124594,67 +127987,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -124669,67 +128011,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -124771,67 +128062,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -124846,67 +128086,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -124919,6 +128108,411 @@ } } ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + }, + "optional": false + }, + "kind": { + "fieldType": { + "type": "literal", + "value": "drawing" + }, + "optional": false + }, + "payloadVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "data": { + "fieldType": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] } }, { @@ -124953,169 +128547,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -125130,169 +128571,220 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] }, @@ -125361,169 +128853,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -125538,169 +128877,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -125742,169 +128928,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -125919,169 +128952,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -126099,37 +128979,241 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -126177,169 +129261,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -126354,169 +129285,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -126558,169 +129336,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -126735,169 +129360,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -126910,40 +129382,86 @@ } } ] - } + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + "type": "array", + "value": { "type": "union", "value": [ { @@ -126959,176 +129477,181 @@ "type": "string" } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] } @@ -127143,169 +129666,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -127347,169 +129717,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -127524,169 +129741,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -127712,35 +129776,39 @@ }, "optional": false } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "ability" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } + } + ] } }, { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -127832,169 +129900,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -128009,169 +129924,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -128213,169 +129975,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -128390,169 +129999,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -128621,169 +130077,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -128798,169 +130101,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -129002,169 +130152,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -129179,169 +130176,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -129437,169 +130281,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -129614,169 +130305,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -129818,169 +130356,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -129995,169 +130380,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -130226,169 +130458,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -130403,169 +130482,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -130607,169 +130533,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -130784,169 +130557,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -130969,126 +130589,333 @@ } } ] - }, - "optional": false - } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "drawing" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" + } }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "boolean" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "number" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -131114,67 +130941,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -131189,67 +130965,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -131257,222 +130982,166 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, - "optional": false - } + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + "type": "array", + "value": { "type": "union", "value": [ { @@ -131488,168 +131157,117 @@ "type": "string" } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false } - ] - } + } + ] }, - { - "keys": { - "type": "string" + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -131666,218 +131284,19 @@ } ] }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false + } + } + ] } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -131903,67 +131322,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -131978,67 +131346,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -132046,200 +131363,294 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false } - ] - } + } + ] }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + }, + "optional": false + }, + "kind": { + "fieldType": { + "type": "literal", + "value": "text" + }, + "optional": false + }, + "payloadVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "data": { + "fieldType": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -132256,422 +131667,121 @@ } ] }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false + } + } + ] + }, + "optional": false + } + } + ] } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -132697,169 +131807,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -132874,169 +131831,322 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] }, @@ -133078,169 +132188,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -133255,169 +132212,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -133430,40 +132234,290 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -133479,176 +132533,385 @@ "type": "string" } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] } @@ -133663,169 +132926,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -133867,169 +132977,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -134044,169 +133001,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -134232,35 +133036,39 @@ }, "optional": false } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "text" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } + } + ] } }, { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -134352,169 +133160,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -134529,169 +133184,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -134733,169 +133235,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -134910,169 +133259,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -135141,169 +133337,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -135318,550 +133361,91 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "boolean" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "number" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "string" } ] }, @@ -135957,169 +133541,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -136134,169 +133565,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -136338,169 +133616,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -136515,169 +133640,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -136746,169 +133718,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -136923,169 +133742,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -137127,169 +133793,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -137304,169 +133817,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -137489,129 +133849,127 @@ } } ] - }, - "optional": false - } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "image" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" + } }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -137628,73 +133986,231 @@ } ] }, - { - "type": "array", - "value": { + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] } @@ -137709,67 +134225,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -137777,18 +134242,115 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -137805,191 +134367,196 @@ } ] }, - { - "type": "array", - "value": { + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false } - ] - }, - "optional": false - } + } + ] + }, + "optional": false } - ] - } + } + ] + } + }, + { + "keys": { + "type": "string" }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -138015,67 +134582,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -138090,67 +134606,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -138158,193 +134623,700 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false } - ] - }, - "optional": false - } - } - ] + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + }, + "optional": false + }, + "kind": { + "fieldType": { + "type": "literal", + "value": "image" + }, + "optional": false + }, + "payloadVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "data": { + "fieldType": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] } }, { @@ -138401,169 +135373,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -138578,169 +135397,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -138782,169 +135448,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -138959,169 +135472,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -139139,37 +135499,241 @@ } } ] - } + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -139217,169 +135781,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -139394,169 +135805,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -139598,169 +135856,220 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] } @@ -139775,169 +136084,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -140006,169 +136162,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -140183,169 +136186,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -140387,169 +136237,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -140564,169 +136261,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -140752,61 +136296,39 @@ }, "optional": false } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "utility" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } + } + ] } - } - ] - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "revision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "type": { - "fieldType": { - "type": "literal", - "value": "lineup" - }, - "optional": false - }, - "value": { - "fieldType": { - "type": "union", - "value": [ + }, { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -140898,169 +136420,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -141075,169 +136444,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -141279,169 +136495,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -141456,169 +136519,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -141687,169 +136597,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -141864,169 +136621,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -142068,169 +136672,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -142245,169 +136696,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -142503,169 +136801,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -142680,169 +136825,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -142884,169 +136876,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -143061,169 +136900,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -143292,169 +136978,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -143469,169 +137002,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -143673,169 +137053,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -143850,169 +137077,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -144035,129 +137109,127 @@ } } ] - }, - "optional": false - } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "lineupOrigin" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" + } }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -144174,73 +137246,231 @@ } ] }, - { - "type": "array", - "value": { + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] } @@ -144255,67 +137485,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -144323,18 +137502,115 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -144351,194 +137627,41 @@ } ] }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false + } + } + ] } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + "type": "array", + "value": { "type": "union", "value": [ { @@ -144554,168 +137677,117 @@ "type": "string" } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false } - ] - } + } + ] }, - { - "keys": { - "type": "string" + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -144732,218 +137804,19 @@ } ] }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false + } + } + ] } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -144969,67 +137842,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -145044,67 +137866,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -145112,186 +137883,347 @@ } } ] - } + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + }, + "optional": false + }, + "kind": { + "fieldType": { + "type": "literal", + "value": "utility" + }, + "optional": false + }, + "payloadVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + } + ] + }, + "optional": false + }, + "publicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "revision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "sortIndex": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "updatedAt": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + } + }, + "optional": false + }, + "lineups": { + "fieldType": { + "type": "array", + "value": { + "type": "object", + "value": { + "createdAt": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "deleted": { + "fieldType": { + "type": "boolean" + }, + "optional": false + }, + "pagePublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "payload": { + "fieldType": { + "type": "object", + "value": { + "data": { + "fieldType": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] } @@ -145306,373 +138238,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -145741,373 +138316,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -146122,373 +138340,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -146530,373 +138391,220 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] } @@ -146911,373 +138619,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -147290,109 +138641,112 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "lineupLanding" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -147418,169 +138772,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -147595,169 +138796,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -147765,18 +138813,217 @@ } } ] - } + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, { - "keys": { - "type": "string" + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -147793,398 +139040,296 @@ } ] }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false } - ] - }, - "optional": false - } + } + ] + }, + "optional": false } - ] - } + } + ] }, - { - "keys": { - "type": "string" + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -148201,371 +139346,167 @@ } ] }, - { - "type": "array", - "value": { + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false } - ] - } + } + ] }, - { - "keys": { - "type": "string" + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -148582,378 +139523,180 @@ } ] }, - { - "type": "array", - "value": { + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false } - ] - }, - "optional": false - } + } + ] + }, + "optional": false } - ] - }, - "optional": false - } + } + ] + }, + "optional": false } - ] - } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, { - "keys": { - "type": "string" + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -149001,373 +139744,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -149382,373 +139768,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -149790,373 +139819,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -150171,373 +139843,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -150550,561 +139865,15 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "lineupLink" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - } - ] - }, - "optional": false - } - } - } - ] - }, - "optional": true - }, - "opId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "reason": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "literal", - "value": "already_exists" - }, - { - "type": "literal", - "value": "element_strategy_mismatch" - }, - { - "type": "literal", - "value": "lineup_strategy_mismatch" - }, - { - "type": "literal", - "value": "missing_expected_revision" - }, - { - "type": "literal", - "value": "not_found" - }, - { - "type": "literal", - "value": "page_strategy_mismatch" - }, - { - "type": "literal", - "value": "revision_mismatch" - } - ] - }, - "optional": false - }, - "status": { - "fieldType": { - "type": "literal", - "value": "rejected" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "code": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "message": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "opId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "rawCode": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "status": { - "fieldType": { - "type": "literal", - "value": "failed" - }, - "optional": false - } - } - } - ] - } - }, - "optional": false - }, - "strategyPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - } - } - }, - "visibility": { - "kind": "public" - } - }, - { - "args": { - "type": "object", - "value": { - "clientProtocolVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "pagePublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "shareToken": { - "fieldType": { - "type": "string" - }, - "optional": true - }, - "strategyPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - } - } - }, - "functionType": "Query", - "identifier": "page.js:getSnapshot", - "returns": { - "type": "object", - "value": { - "assets": { - "fieldType": { - "type": "array", - "value": { - "type": "object", - "value": { - "byteSize": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "optional": false - }, - "fileExtension": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "height": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "optional": false - }, - "legacyStoragePath": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "optional": false - }, - "mimeType": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "optional": false - }, - "provider": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "literal", - "value": "convex" - }, - { - "type": "literal", - "value": "r2" - } - ] - }, - "optional": false - }, - "publicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "uploadedAt": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "optional": false - }, - "uploadStatus": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "literal", - "value": "pending" - }, - { - "type": "literal", - "value": "active" - }, - { - "type": "literal", - "value": "failed" - }, - { - "type": "literal", - "value": "deleted" - } - ] - }, - "optional": false - }, - "url": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "optional": false - }, - "width": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "optional": false - } - } - } - }, - "optional": false - }, - "content": { - "fieldType": { - "type": "object", - "value": { - "createdAt": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "revision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "settings": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "object", - "value": { - "abilitySize": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "agentSize": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "useNeutralTeamColors": { - "fieldType": { - "type": "boolean" - }, - "optional": false - } - } - }, - { - "type": "null" - } - ] - }, - "optional": false - }, - "updatedAt": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - "optional": false - }, - "elements": { - "fieldType": { - "type": "array", - "value": { - "type": "object", - "value": { - "createdAt": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "deleted": { - "fieldType": { - "type": "boolean" - }, - "optional": false - }, - "elementType": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "literal", - "value": "agent" - }, - { - "type": "literal", - "value": "ability" - }, - { - "type": "literal", - "value": "drawing" - }, - { - "type": "literal", - "value": "text" - }, - { - "type": "literal", - "value": "image" - }, - { - "type": "literal", - "value": "utility" - } - ] - }, - "optional": false - }, - "pagePublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "payload": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + } }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -151273,40 +140042,86 @@ } } ] - } + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + "type": "array", + "value": { "type": "union", "value": [ { @@ -151322,12 +140137,170 @@ "type": "string" } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { - "type": "union", - "value": [ + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ { "type": "null" }, @@ -151455,59 +140428,161 @@ } } ] - } + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -151574,18 +140649,64 @@ } } ] - } + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -151601,10 +140722,15 @@ "type": "string" } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -151620,71 +140746,20 @@ "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "optional": false } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -151751,127 +140826,25 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false } - ] - }, - "optional": false - } + } + ] + }, + "optional": false } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -152244,37272 +141217,2617 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } ] }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "agent" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "ability" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "drawing" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "text" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "image" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "utility" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - } - ] - }, - "optional": false - }, - "publicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "revision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "sortIndex": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "strategyPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "updatedAt": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - } - }, - "optional": false - }, - "lineups": { - "fieldType": { - "type": "array", - "value": { - "type": "object", - "value": { - "createdAt": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "deleted": { - "fieldType": { - "type": "boolean" - }, - "optional": false - }, - "pagePublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "payload": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "lineupOrigin" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "lineupLanding" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "lineupLink" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - } - ] - }, - "optional": false - }, - "publicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "revision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "sortIndex": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "strategyPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "updatedAt": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - } - }, - "optional": false - }, - "page": { - "fieldType": { - "type": "object", - "value": { - "createdAt": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "isAttack": { - "fieldType": { - "type": "boolean" - }, - "optional": false - }, - "isAutoNamed": { - "fieldType": { - "type": "boolean" - }, - "optional": true - }, - "name": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "publicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "revision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "sortIndex": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "strategyPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "updatedAt": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - "optional": false - } - } - }, - "visibility": { - "kind": "public" - } - }, - { - "args": { - "type": "object", - "value": { - "clientProtocolVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "expectedRevision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "isAttack": { - "fieldType": { - "type": "boolean" - }, - "optional": false - }, - "isAutoNamed": { - "fieldType": { - "type": "boolean" - }, - "optional": true - }, - "name": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "pagePublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "settings": { - "fieldType": { - "type": "object", - "value": { - "abilitySize": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "agentSize": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "useNeutralTeamColors": { - "fieldType": { - "type": "boolean" - }, - "optional": false - } - } - }, - "optional": true - }, - "sortIndex": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "strategyPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - } - } - }, - "functionType": "Mutation", - "identifier": "pages.js:add", - "returns": { - "type": "union", - "value": [ - { - "type": "object", - "value": { - "ok": { - "fieldType": { - "type": "literal", - "value": true - }, - "optional": false - }, - "revision": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "ok": { - "fieldType": { - "type": "literal", - "value": true - }, - "optional": false - }, - "reused": { - "fieldType": { - "type": "literal", - "value": true - }, - "optional": false - }, - "revision": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - } - ] - }, - "visibility": { - "kind": "public" - } - }, - { - "args": { - "type": "object", - "value": { - "clientProtocolVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "expectedRevision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "pagePublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "strategyPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - } - } - }, - "functionType": "Mutation", - "identifier": "pages.js:delete", - "returns": { - "type": "union", - "value": [ - { - "type": "object", - "value": { - "ok": { - "fieldType": { - "type": "literal", - "value": true - }, - "optional": false - }, - "revision": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "ok": { - "fieldType": { - "type": "literal", - "value": true - }, - "optional": false - }, - "reused": { - "fieldType": { - "type": "literal", - "value": true - }, - "optional": false - }, - "revision": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - } - ] - }, - "visibility": { - "kind": "public" - } - }, - { - "args": { - "type": "object", - "value": { - "strategyPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - } - } - }, - "functionType": "Query", - "identifier": "pages.js:listForStrategy", - "returns": { - "type": "array", - "value": { - "type": "object", - "value": { - "createdAt": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "isAttack": { - "fieldType": { - "type": "boolean" - }, - "optional": false - }, - "isAutoNamed": { - "fieldType": { - "type": "boolean" - }, - "optional": true - }, - "name": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "publicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "revision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "sortIndex": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "strategyPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "updatedAt": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - } - }, - "visibility": { - "kind": "public" - } - }, - { - "args": { - "type": "object", - "value": { - "strategyPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - } - } - }, - "functionType": "Query", - "identifier": "pages.js:listTrashed", - "returns": { - "type": "array", - "value": { - "type": "object", - "value": { - "deletedAt": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "deletedByName": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "optional": false - }, - "deletedByYou": { - "fieldType": { - "type": "boolean" - }, - "optional": false - }, - "name": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "publicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "restorableUntil": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - } - }, - "visibility": { - "kind": "public" - } - }, - { - "args": { - "type": "object", - "value": { - "clientProtocolVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "expectedRevision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "isAutoNamed": { - "fieldType": { - "type": "boolean" - }, - "optional": true - }, - "name": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "pagePublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "strategyPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - } - } - }, - "functionType": "Mutation", - "identifier": "pages.js:rename", - "returns": { - "type": "union", - "value": [ - { - "type": "object", - "value": { - "ok": { - "fieldType": { - "type": "literal", - "value": true - }, - "optional": false - }, - "revision": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "ok": { - "fieldType": { - "type": "literal", - "value": true - }, - "optional": false - }, - "reused": { - "fieldType": { - "type": "literal", - "value": true - }, - "optional": false - }, - "revision": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - } - ] - }, - "visibility": { - "kind": "public" - } - }, - { - "args": { - "type": "object", - "value": { - "clientProtocolVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "expectedRevision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "orderedPagePublicIds": { - "fieldType": { - "type": "array", - "value": { - "type": "string" - } - }, - "optional": false - }, - "strategyPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - } - } - }, - "functionType": "Mutation", - "identifier": "pages.js:reorder", - "returns": { - "type": "union", - "value": [ - { - "type": "object", - "value": { - "ok": { - "fieldType": { - "type": "literal", - "value": true - }, - "optional": false - }, - "revision": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "ok": { - "fieldType": { - "type": "literal", - "value": true - }, - "optional": false - }, - "reused": { - "fieldType": { - "type": "literal", - "value": true - }, - "optional": false - }, - "revision": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - } - ] - }, - "visibility": { - "kind": "public" - } - }, - { - "args": { - "type": "object", - "value": { - "clientProtocolVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "pagePublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "strategyPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - } - } - }, - "functionType": "Mutation", - "identifier": "pages.js:restore", - "returns": { - "type": "union", - "value": [ - { - "type": "object", - "value": { - "ok": { - "fieldType": { - "type": "literal", - "value": true - }, - "optional": false - }, - "revision": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "ok": { - "fieldType": { - "type": "literal", - "value": true - }, - "optional": false - }, - "reused": { - "fieldType": { - "type": "literal", - "value": true - }, - "optional": false - }, - "revision": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - } - ] - }, - "visibility": { - "kind": "public" - } - }, - { - "args": { - "type": "object", - "value": { - "clientProtocolVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "strategyPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - } - } - }, - "functionType": "Mutation", - "identifier": "presence.js:issueRoomPass", - "returns": { - "type": "union", - "value": [ - { - "type": "object", - "value": { - "expiresAt": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "pass": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "url": { - "fieldType": { - "type": "string" - }, - "optional": false - } - } - }, - { - "type": "null" - } - ] - }, - "visibility": { - "kind": "public" - } - }, - { - "args": { - "type": "object", - "value": { - "clientProtocolVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "role": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "literal", - "value": "viewer" - }, - { - "type": "literal", - "value": "editor" - } - ] - }, - "optional": false - }, - "targetPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "targetType": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "literal", - "value": "strategy" - }, - { - "type": "literal", - "value": "folder" - } - ] - }, - "optional": false - }, - "token": { - "fieldType": { - "type": "string" - }, - "optional": false - } - } - }, - "functionType": "Mutation", - "identifier": "shares.js:create", - "returns": { - "type": "object", - "value": { - "ok": { - "fieldType": { - "type": "literal", - "value": true - }, - "optional": false - } - } - }, - "visibility": { - "kind": "public" - } - }, - { - "args": { - "type": "object", - "value": { - "targetPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "targetType": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "literal", - "value": "strategy" - }, - { - "type": "literal", - "value": "folder" - } - ] - }, - "optional": false - } - } - }, - "functionType": "Query", - "identifier": "shares.js:list", - "returns": { - "type": "array", - "value": { - "type": "object", - "value": { - "createdAt": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "revokedAt": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "optional": false - }, - "role": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "literal", - "value": "viewer" - }, - { - "type": "literal", - "value": "editor" - } - ] - }, - "optional": false - }, - "token": { - "fieldType": { - "type": "string" - }, - "optional": false - } - } - } - }, - "visibility": { - "kind": "public" - } - }, - { - "args": { - "type": "object", - "value": { - "clientProtocolVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "token": { - "fieldType": { - "type": "string" - }, - "optional": false - } - } - }, - "functionType": "Mutation", - "identifier": "shares.js:redeem", - "returns": { - "type": "union", - "value": [ - { - "type": "object", - "value": { - "alreadyHadAccess": { - "fieldType": { - "type": "boolean" - }, - "optional": true - }, - "folderPublicId": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "optional": false - }, - "ok": { - "fieldType": { - "type": "literal", - "value": true - }, - "optional": false - }, - "role": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "literal", - "value": "owner" - }, - { - "type": "literal", - "value": "editor" - }, - { - "type": "literal", - "value": "viewer" - } - ] - }, - "optional": false - }, - "strategyPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "targetType": { - "fieldType": { - "type": "literal", - "value": "strategy" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "alreadyHadAccess": { - "fieldType": { - "type": "boolean" - }, - "optional": true - }, - "folderPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "ok": { - "fieldType": { - "type": "literal", - "value": true - }, - "optional": false - }, - "role": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "literal", - "value": "owner" - }, - { - "type": "literal", - "value": "editor" - }, - { - "type": "literal", - "value": "viewer" - } - ] - }, - "optional": false - }, - "targetType": { - "fieldType": { - "type": "literal", - "value": "folder" - }, - "optional": false - } - } - } - ] - }, - "visibility": { - "kind": "public" - } - }, - { - "args": { - "type": "object", - "value": { - "token": { - "fieldType": { - "type": "string" - }, - "optional": false - } - } - }, - "functionType": "Query", - "identifier": "shares.js:resolve", - "returns": { - "type": "union", - "value": [ - { - "type": "object", - "value": { - "role": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "literal", - "value": "viewer" - }, - { - "type": "literal", - "value": "editor" - } - ] - }, - "optional": false - }, - "strategyPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "targetType": { - "fieldType": { - "type": "literal", - "value": "strategy" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "role": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "literal", - "value": "viewer" - }, - { - "type": "literal", - "value": "editor" - } - ] - }, - "optional": false - }, - "targetType": { - "fieldType": { - "type": "literal", - "value": "folder" - }, - "optional": false - } - } - } - ] - }, - "visibility": { - "kind": "public" - } - }, - { - "args": { - "type": "object", - "value": { - "clientProtocolVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "targetPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "targetType": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "literal", - "value": "strategy" - }, - { - "type": "literal", - "value": "folder" - } - ] - }, - "optional": false - }, - "token": { - "fieldType": { - "type": "string" - }, - "optional": false - } - } - }, - "functionType": "Mutation", - "identifier": "shares.js:revoke", - "returns": { - "type": "object", - "value": { - "ok": { - "fieldType": { - "type": "literal", - "value": true - }, - "optional": false - } - } - }, - "visibility": { - "kind": "public" - } - }, - { - "args": { - "type": "object", - "value": { - "clientProtocolVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "folderPublicId": { - "fieldType": { - "type": "string" - }, - "optional": true - }, - "mapData": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "name": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "publicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "themeOverridePalette": { - "fieldType": { - "type": "object", - "value": { - "base": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "detail": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "highlight": { - "fieldType": { - "type": "string" - }, - "optional": false - } - } - }, - "optional": true - }, - "themeProfileId": { - "fieldType": { - "type": "string" - }, - "optional": true - } - } - }, - "functionType": "Mutation", - "identifier": "strategies.js:create", - "returns": { - "type": "union", - "value": [ - { - "type": "object", - "value": { - "ok": { - "fieldType": { - "type": "literal", - "value": true - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "ok": { - "fieldType": { - "type": "literal", - "value": true - }, - "optional": false - }, - "reused": { - "fieldType": { - "type": "literal", - "value": true - }, - "optional": false - } - } - } - ] - }, - "visibility": { - "kind": "public" - } - }, - { - "args": { - "type": "object", - "value": { - "clientProtocolVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "folderPublicId": { - "fieldType": { - "type": "string" - }, - "optional": true - }, - "initialPageIsAttack": { - "fieldType": { - "type": "boolean" - }, - "optional": false - }, - "initialPageIsAutoNamed": { - "fieldType": { - "type": "boolean" - }, - "optional": true - }, - "initialPageName": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "initialPagePublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "initialPageSettings": { - "fieldType": { - "type": "object", - "value": { - "abilitySize": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "agentSize": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "useNeutralTeamColors": { - "fieldType": { - "type": "boolean" - }, - "optional": false - } - } - }, - "optional": true - }, - "mapData": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "name": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "publicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "themeOverridePalette": { - "fieldType": { - "type": "object", - "value": { - "base": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "detail": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "highlight": { - "fieldType": { - "type": "string" - }, - "optional": false - } - } - }, - "optional": true - }, - "themeProfileId": { - "fieldType": { - "type": "string" - }, - "optional": true - } - } - }, - "functionType": "Mutation", - "identifier": "strategies.js:createWithInitialPage", - "returns": { - "type": "union", - "value": [ - { - "type": "object", - "value": { - "ok": { - "fieldType": { - "type": "literal", - "value": true - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "ok": { - "fieldType": { - "type": "literal", - "value": true - }, - "optional": false - }, - "reused": { - "fieldType": { - "type": "literal", - "value": true - }, - "optional": false - } - } - } - ] - }, - "visibility": { - "kind": "public" - } - }, - { - "args": { - "type": "object", - "value": { - "clientProtocolVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "expectedRevision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "strategyPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - } - } - }, - "functionType": "Mutation", - "identifier": "strategies.js:delete", - "returns": { - "type": "object", - "value": { - "ok": { - "fieldType": { - "type": "literal", - "value": true - }, - "optional": false - } - } - }, - "visibility": { - "kind": "public" - } - }, - { - "args": { - "type": "object", - "value": { - "clientProtocolVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "folderPublicId": { - "fieldType": { - "type": "string" - }, - "optional": true - }, - "name": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "publicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "sourceStrategyPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - } - } - }, - "functionType": "Mutation", - "identifier": "strategies.js:duplicate", - "returns": { - "type": "union", - "value": [ - { - "type": "object", - "value": { - "ok": { - "fieldType": { - "type": "literal", - "value": true - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "ok": { - "fieldType": { - "type": "literal", - "value": true - }, - "optional": false - }, - "reused": { - "fieldType": { - "type": "literal", - "value": true - }, - "optional": false - } - } - } - ] - }, - "visibility": { - "kind": "public" - } - }, - { - "args": { - "type": "object", - "value": { - "strategyPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - } - } - }, - "functionType": "Query", - "identifier": "strategies.js:getHeader", - "returns": { - "type": "object", - "value": { - "createdAt": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "mapData": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "name": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "publicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "revision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "role": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "literal", - "value": "owner" - }, - { - "type": "literal", - "value": "editor" - }, - { - "type": "literal", - "value": "viewer" - } - ] - }, - "optional": false - }, - "themeOverridePalette": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "object", - "value": { - "base": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "detail": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "highlight": { - "fieldType": { - "type": "string" - }, - "optional": false - } - } - }, - { - "type": "null" - } - ] - }, - "optional": false - }, - "themeProfileId": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "optional": false - }, - "updatedAt": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - "visibility": { - "kind": "public" - } - }, - { - "args": { - "type": "object", - "value": { - "folderPublicId": { - "fieldType": { - "type": "string" - }, - "optional": true - }, - "scope": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "literal", - "value": "owned" - }, - { - "type": "literal", - "value": "shared" - }, - { - "type": "literal", - "value": "all" - } - ] - }, - "optional": true - } - } - }, - "functionType": "Query", - "identifier": "strategies.js:listForFolder", - "returns": { - "type": "array", - "value": { - "type": "object", - "value": { - "attackLabel": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "literal", - "value": "Unknown" - }, - { - "type": "literal", - "value": "Mixed" - }, - { - "type": "literal", - "value": "Attack" - }, - { - "type": "literal", - "value": "Defend" - } - ] - }, - "optional": false - }, - "createdAt": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "folderPublicId": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "optional": false - }, - "mapData": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "name": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "publicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "revision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "role": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "literal", - "value": "owner" - }, - { - "type": "literal", - "value": "editor" - }, - { - "type": "literal", - "value": "viewer" - } - ] - }, - "optional": false - }, - "themeOverridePalette": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "object", - "value": { - "base": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "detail": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "highlight": { - "fieldType": { - "type": "string" - }, - "optional": false - } - } - }, - { - "type": "null" - } - ] - }, - "optional": false - }, - "themeProfileId": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "optional": false - }, - "updatedAt": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - } - }, - "visibility": { - "kind": "public" - } - }, - { - "args": { - "type": "object", - "value": {} - }, - "functionType": "Query", - "identifier": "strategies.js:listSharedWithMe", - "returns": { - "type": "array", - "value": { - "type": "object", - "value": { - "attackLabel": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "literal", - "value": "Unknown" - }, - { - "type": "literal", - "value": "Mixed" - }, - { - "type": "literal", - "value": "Attack" - }, - { - "type": "literal", - "value": "Defend" - } - ] - }, - "optional": false - }, - "createdAt": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "folderPublicId": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "optional": false - }, - "mapData": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "name": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "publicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "revision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "role": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "literal", - "value": "owner" - }, - { - "type": "literal", - "value": "editor" - }, - { - "type": "literal", - "value": "viewer" - } - ] - }, - "optional": false - }, - "themeOverridePalette": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "object", - "value": { - "base": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "detail": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "highlight": { - "fieldType": { - "type": "string" - }, - "optional": false - } - } - }, - { - "type": "null" - } - ] - }, - "optional": false - }, - "themeProfileId": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "optional": false - }, - "updatedAt": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - } - }, - "visibility": { - "kind": "public" - } - }, - { - "args": { - "type": "object", - "value": { - "clientProtocolVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "expectedRevision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "folderPublicId": { - "fieldType": { - "type": "string" - }, - "optional": true - }, - "strategyPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - } - } - }, - "functionType": "Mutation", - "identifier": "strategies.js:move", - "returns": { - "type": "union", - "value": [ - { - "type": "object", - "value": { - "ok": { - "fieldType": { - "type": "literal", - "value": true - }, - "optional": false - }, - "revision": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "ok": { - "fieldType": { - "type": "literal", - "value": true - }, - "optional": false - }, - "reused": { - "fieldType": { - "type": "literal", - "value": true - }, - "optional": false - }, - "revision": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - } - ] - }, - "visibility": { - "kind": "public" - } - }, - { - "args": { - "type": "object", - "value": { - "clearThemeOverridePalette": { - "fieldType": { - "type": "boolean" - }, - "optional": true - }, - "clearThemeProfileId": { - "fieldType": { - "type": "boolean" - }, - "optional": true - }, - "clientProtocolVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "expectedRevision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "mapData": { - "fieldType": { - "type": "string" - }, - "optional": true - }, - "name": { - "fieldType": { - "type": "string" - }, - "optional": true - }, - "strategyPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "themeOverridePalette": { - "fieldType": { - "type": "object", - "value": { - "base": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "detail": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "highlight": { - "fieldType": { - "type": "string" - }, - "optional": false - } - } - }, - "optional": true - }, - "themeProfileId": { - "fieldType": { - "type": "string" - }, - "optional": true - } - } - }, - "functionType": "Mutation", - "identifier": "strategies.js:update", - "returns": { - "type": "union", - "value": [ - { - "type": "object", - "value": { - "ok": { - "fieldType": { - "type": "literal", - "value": true - }, - "optional": false - }, - "revision": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "ok": { - "fieldType": { - "type": "literal", - "value": true - }, - "optional": false - }, - "reused": { - "fieldType": { - "type": "literal", - "value": true - }, - "optional": false - }, - "revision": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - } - ] - }, - "visibility": { - "kind": "public" - } - }, - { - "args": { - "type": "object", - "value": { - "acceptsTrashedPagesLeftOut": { - "fieldType": { - "type": "boolean" - }, - "optional": true - }, - "clientProtocolVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "shareToken": { - "fieldType": { - "type": "string" - }, - "optional": true - }, - "strategyPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - } - } - }, - "functionType": "Query", - "identifier": "strategy.js:getFullSnapshot", - "returns": { - "type": "object", - "value": { - "assets": { - "fieldType": { - "type": "array", - "value": { - "type": "object", - "value": { - "byteSize": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "optional": false - }, - "fileExtension": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "height": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "optional": false - }, - "legacyStoragePath": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "optional": false - }, - "mimeType": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "optional": false - }, - "provider": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "literal", - "value": "convex" - }, - { - "type": "literal", - "value": "r2" - } - ] - }, - "optional": false - }, - "publicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "uploadedAt": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "optional": false - }, - "uploadStatus": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "literal", - "value": "pending" - }, - { - "type": "literal", - "value": "active" - }, - { - "type": "literal", - "value": "failed" - }, - { - "type": "literal", - "value": "deleted" - } - ] - }, - "optional": false - }, - "url": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "optional": false - }, - "width": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - }, - "optional": false - } - } - } - }, - "optional": false - }, - "elements": { - "fieldType": { - "type": "array", - "value": { - "type": "object", - "value": { - "createdAt": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "deleted": { - "fieldType": { - "type": "boolean" - }, - "optional": false - }, - "elementType": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "literal", - "value": "agent" - }, - { - "type": "literal", - "value": "ability" - }, - { - "type": "literal", - "value": "drawing" - }, - { - "type": "literal", - "value": "text" - }, - { - "type": "literal", - "value": "image" - }, - { - "type": "literal", - "value": "utility" - } - ] - }, - "optional": false - }, - "pagePublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "payload": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "agent" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "ability" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" + "optional": false + } + } + ] + }, + "optional": false + } + } + ] }, "optional": false } - } + }, + "optional": false + }, + "kind": { + "fieldType": { + "type": "literal", + "value": "lineup" + }, + "optional": false + }, + "payloadVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + "optional": false + }, + "publicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "revision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "sortIndex": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "updatedAt": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + } + }, + "optional": false + }, + "page": { + "fieldType": { + "type": "object", + "value": { + "createdAt": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "isAttack": { + "fieldType": { + "type": "boolean" + }, + "optional": false + }, + "isAutoNamed": { + "fieldType": { + "type": "boolean" + }, + "optional": true + }, + "name": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "publicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "revision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "sortIndex": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "updatedAt": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + "optional": false + } + } + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "clientProtocolVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "expectedRevision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "isAttack": { + "fieldType": { + "type": "boolean" + }, + "optional": false + }, + "isAutoNamed": { + "fieldType": { + "type": "boolean" + }, + "optional": true + }, + "name": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "pagePublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "settings": { + "fieldType": { + "type": "object", + "value": { + "abilitySize": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "agentSize": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "useNeutralTeamColors": { + "fieldType": { + "type": "boolean" + }, + "optional": false + } + } + }, + "optional": true + }, + "sortIndex": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Mutation", + "identifier": "pages.js:add", + "returns": { + "type": "union", + "value": [ + { + "type": "object", + "value": { + "ok": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + }, + "revision": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "ok": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + }, + "reused": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + }, + "revision": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + } + ] + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "clientProtocolVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "expectedRevision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "pagePublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Mutation", + "identifier": "pages.js:delete", + "returns": { + "type": "union", + "value": [ + { + "type": "object", + "value": { + "ok": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + }, + "revision": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "ok": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + }, + "reused": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + }, + "revision": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + } + ] + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Query", + "identifier": "pages.js:listForStrategy", + "returns": { + "type": "array", + "value": { + "type": "object", + "value": { + "createdAt": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "isAttack": { + "fieldType": { + "type": "boolean" + }, + "optional": false + }, + "isAutoNamed": { + "fieldType": { + "type": "boolean" + }, + "optional": true + }, + "name": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "publicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "revision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "sortIndex": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "updatedAt": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + } + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Query", + "identifier": "pages.js:listTrashed", + "returns": { + "type": "array", + "value": { + "type": "object", + "value": { + "deletedAt": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "deletedByName": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "deletedByYou": { + "fieldType": { + "type": "boolean" + }, + "optional": false + }, + "name": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "publicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "restorableUntil": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + } + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "clientProtocolVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "expectedRevision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "isAutoNamed": { + "fieldType": { + "type": "boolean" + }, + "optional": true + }, + "name": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "pagePublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Mutation", + "identifier": "pages.js:rename", + "returns": { + "type": "union", + "value": [ + { + "type": "object", + "value": { + "ok": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + }, + "revision": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "ok": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + }, + "reused": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + }, + "revision": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + } + ] + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "clientProtocolVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "expectedRevision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "orderedPagePublicIds": { + "fieldType": { + "type": "array", + "value": { + "type": "string" + } + }, + "optional": false + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Mutation", + "identifier": "pages.js:reorder", + "returns": { + "type": "union", + "value": [ + { + "type": "object", + "value": { + "ok": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + }, + "revision": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "ok": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + }, + "reused": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + }, + "revision": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + } + ] + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "clientProtocolVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "pagePublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Mutation", + "identifier": "pages.js:restore", + "returns": { + "type": "union", + "value": [ + { + "type": "object", + "value": { + "ok": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + }, + "revision": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "ok": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + }, + "reused": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + }, + "revision": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + } + ] + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "clientProtocolVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Mutation", + "identifier": "presence.js:issueRoomPass", + "returns": { + "type": "union", + "value": [ + { + "type": "object", + "value": { + "expiresAt": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "pass": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "url": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + { + "type": "null" + } + ] + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "clientProtocolVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "role": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "literal", + "value": "viewer" + }, + { + "type": "literal", + "value": "editor" + } + ] + }, + "optional": false + }, + "targetPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "targetType": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "literal", + "value": "strategy" + }, + { + "type": "literal", + "value": "folder" + } + ] + }, + "optional": false + }, + "token": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Mutation", + "identifier": "shares.js:create", + "returns": { + "type": "object", + "value": { + "ok": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + } + } + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "targetPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "targetType": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "literal", + "value": "strategy" + }, + { + "type": "literal", + "value": "folder" + } + ] + }, + "optional": false + } + } + }, + "functionType": "Query", + "identifier": "shares.js:list", + "returns": { + "type": "array", + "value": { + "type": "object", + "value": { + "createdAt": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "revokedAt": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "role": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "literal", + "value": "viewer" + }, + { + "type": "literal", + "value": "editor" + } + ] + }, + "optional": false + }, + "token": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + } + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "clientProtocolVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "token": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Mutation", + "identifier": "shares.js:redeem", + "returns": { + "type": "union", + "value": [ + { + "type": "object", + "value": { + "alreadyHadAccess": { + "fieldType": { + "type": "boolean" + }, + "optional": true + }, + "folderPublicId": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "ok": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + }, + "role": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "literal", + "value": "owner" + }, + { + "type": "literal", + "value": "editor" + }, + { + "type": "literal", + "value": "viewer" + } + ] + }, + "optional": false + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "targetType": { + "fieldType": { + "type": "literal", + "value": "strategy" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "alreadyHadAccess": { + "fieldType": { + "type": "boolean" + }, + "optional": true + }, + "folderPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "ok": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + }, + "role": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "literal", + "value": "owner" + }, + { + "type": "literal", + "value": "editor" + }, + { + "type": "literal", + "value": "viewer" + } + ] + }, + "optional": false + }, + "targetType": { + "fieldType": { + "type": "literal", + "value": "folder" + }, + "optional": false + } + } + } + ] + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "token": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Query", + "identifier": "shares.js:resolve", + "returns": { + "type": "union", + "value": [ + { + "type": "object", + "value": { + "role": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "literal", + "value": "viewer" + }, + { + "type": "literal", + "value": "editor" + } + ] + }, + "optional": false + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "targetType": { + "fieldType": { + "type": "literal", + "value": "strategy" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "role": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "literal", + "value": "viewer" + }, + { + "type": "literal", + "value": "editor" + } + ] + }, + "optional": false + }, + "targetType": { + "fieldType": { + "type": "literal", + "value": "folder" + }, + "optional": false + } + } + } + ] + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "clientProtocolVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "targetPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "targetType": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "literal", + "value": "strategy" + }, + { + "type": "literal", + "value": "folder" + } + ] + }, + "optional": false + }, + "token": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Mutation", + "identifier": "shares.js:revoke", + "returns": { + "type": "object", + "value": { + "ok": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + } + } + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "clientProtocolVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "folderPublicId": { + "fieldType": { + "type": "string" + }, + "optional": true + }, + "mapData": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "name": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "publicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "themeOverridePalette": { + "fieldType": { + "type": "object", + "value": { + "base": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "detail": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "highlight": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "optional": true + }, + "themeProfileId": { + "fieldType": { + "type": "string" + }, + "optional": true + } + } + }, + "functionType": "Mutation", + "identifier": "strategies.js:create", + "returns": { + "type": "union", + "value": [ + { + "type": "object", + "value": { + "ok": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "ok": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + }, + "reused": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + } + } + } + ] + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "clientProtocolVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "folderPublicId": { + "fieldType": { + "type": "string" + }, + "optional": true + }, + "initialPageIsAttack": { + "fieldType": { + "type": "boolean" + }, + "optional": false + }, + "initialPageIsAutoNamed": { + "fieldType": { + "type": "boolean" + }, + "optional": true + }, + "initialPageName": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "initialPagePublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "initialPageSettings": { + "fieldType": { + "type": "object", + "value": { + "abilitySize": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "agentSize": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "useNeutralTeamColors": { + "fieldType": { + "type": "boolean" + }, + "optional": false + } + } + }, + "optional": true + }, + "mapData": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "name": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "publicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "themeOverridePalette": { + "fieldType": { + "type": "object", + "value": { + "base": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "detail": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "highlight": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "optional": true + }, + "themeProfileId": { + "fieldType": { + "type": "string" + }, + "optional": true + } + } + }, + "functionType": "Mutation", + "identifier": "strategies.js:createWithInitialPage", + "returns": { + "type": "union", + "value": [ + { + "type": "object", + "value": { + "ok": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "ok": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + }, + "reused": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + } + } + } + ] + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "clientProtocolVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "expectedRevision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Mutation", + "identifier": "strategies.js:delete", + "returns": { + "type": "object", + "value": { + "ok": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + } + } + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "clientProtocolVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "folderPublicId": { + "fieldType": { + "type": "string" + }, + "optional": true + }, + "name": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "publicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "sourceStrategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Mutation", + "identifier": "strategies.js:duplicate", + "returns": { + "type": "union", + "value": [ + { + "type": "object", + "value": { + "ok": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "ok": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + }, + "reused": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + } + } + } + ] + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Query", + "identifier": "strategies.js:getHeader", + "returns": { + "type": "object", + "value": { + "createdAt": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "mapData": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "name": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "publicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "revision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "role": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "literal", + "value": "owner" + }, + { + "type": "literal", + "value": "editor" + }, + { + "type": "literal", + "value": "viewer" + } + ] + }, + "optional": false + }, + "themeOverridePalette": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "object", + "value": { + "base": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "detail": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "highlight": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "themeProfileId": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "updatedAt": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "folderPublicId": { + "fieldType": { + "type": "string" + }, + "optional": true + }, + "scope": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "literal", + "value": "owned" + }, + { + "type": "literal", + "value": "shared" + }, + { + "type": "literal", + "value": "all" + } + ] + }, + "optional": true + } + } + }, + "functionType": "Query", + "identifier": "strategies.js:listForFolder", + "returns": { + "type": "array", + "value": { + "type": "object", + "value": { + "attackLabel": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "literal", + "value": "Unknown" + }, + { + "type": "literal", + "value": "Mixed" + }, + { + "type": "literal", + "value": "Attack" + }, + { + "type": "literal", + "value": "Defend" + } + ] + }, + "optional": false + }, + "createdAt": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "folderPublicId": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "mapData": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "name": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "publicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "revision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "role": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "literal", + "value": "owner" + }, + { + "type": "literal", + "value": "editor" + }, + { + "type": "literal", + "value": "viewer" + } + ] + }, + "optional": false + }, + "themeOverridePalette": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "object", + "value": { + "base": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "detail": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "highlight": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "themeProfileId": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "updatedAt": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + } + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": {} + }, + "functionType": "Query", + "identifier": "strategies.js:listSharedWithMe", + "returns": { + "type": "array", + "value": { + "type": "object", + "value": { + "attackLabel": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "literal", + "value": "Unknown" + }, + { + "type": "literal", + "value": "Mixed" + }, + { + "type": "literal", + "value": "Attack" + }, + { + "type": "literal", + "value": "Defend" + } + ] + }, + "optional": false + }, + "createdAt": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "folderPublicId": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "mapData": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "name": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "publicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "revision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "role": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "literal", + "value": "owner" + }, + { + "type": "literal", + "value": "editor" + }, + { + "type": "literal", + "value": "viewer" + } + ] + }, + "optional": false + }, + "themeOverridePalette": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "object", + "value": { + "base": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "detail": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "highlight": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "themeProfileId": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "updatedAt": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + } + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "clientProtocolVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "expectedRevision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "folderPublicId": { + "fieldType": { + "type": "string" + }, + "optional": true + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Mutation", + "identifier": "strategies.js:move", + "returns": { + "type": "union", + "value": [ + { + "type": "object", + "value": { + "ok": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + }, + "revision": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "ok": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + }, + "reused": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + }, + "revision": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + } + ] + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "clearThemeOverridePalette": { + "fieldType": { + "type": "boolean" + }, + "optional": true + }, + "clearThemeProfileId": { + "fieldType": { + "type": "boolean" + }, + "optional": true + }, + "clientProtocolVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "expectedRevision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "mapData": { + "fieldType": { + "type": "string" + }, + "optional": true + }, + "name": { + "fieldType": { + "type": "string" + }, + "optional": true + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "themeOverridePalette": { + "fieldType": { + "type": "object", + "value": { + "base": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "detail": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "highlight": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "optional": true + }, + "themeProfileId": { + "fieldType": { + "type": "string" + }, + "optional": true + } + } + }, + "functionType": "Mutation", + "identifier": "strategies.js:update", + "returns": { + "type": "union", + "value": [ + { + "type": "object", + "value": { + "ok": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + }, + "revision": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "ok": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + }, + "reused": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + }, + "revision": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + } + ] + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "acceptsTrashedPagesLeftOut": { + "fieldType": { + "type": "boolean" + }, + "optional": true + }, + "clientProtocolVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "shareToken": { + "fieldType": { + "type": "string" + }, + "optional": true + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Query", + "identifier": "strategy.js:getFullSnapshot", + "returns": { + "type": "object", + "value": { + "assets": { + "fieldType": { + "type": "array", + "value": { + "type": "object", + "value": { + "byteSize": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "fileExtension": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "height": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "legacyStoragePath": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "mimeType": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "provider": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "literal", + "value": "convex" + }, + { + "type": "literal", + "value": "r2" + } + ] + }, + "optional": false + }, + "publicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "uploadedAt": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "uploadStatus": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "literal", + "value": "pending" + }, + { + "type": "literal", + "value": "active" + }, + { + "type": "literal", + "value": "failed" + }, + { + "type": "literal", + "value": "deleted" + } + ] + }, + "optional": false + }, + "url": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "width": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "optional": false + } + } + } + }, + "optional": false + }, + "elements": { + "fieldType": { + "type": "array", + "value": { + "type": "object", + "value": { + "createdAt": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "deleted": { + "fieldType": { + "type": "boolean" + }, + "optional": false + }, + "elementType": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "literal", + "value": "agent" + }, + { + "type": "literal", + "value": "ability" + }, + { + "type": "literal", + "value": "drawing" }, + { + "type": "literal", + "value": "text" + }, + { + "type": "literal", + "value": "image" + }, + { + "type": "literal", + "value": "utility" + } + ] + }, + "optional": false + }, + "pagePublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "payload": { + "fieldType": { + "type": "union", + "value": [ { "type": "object", "value": { @@ -192758,7 +147076,7 @@ "kind": { "fieldType": { "type": "literal", - "value": "drawing" + "value": "agent" }, "optional": false }, @@ -196018,7 +150336,7 @@ "kind": { "fieldType": { "type": "literal", - "value": "text" + "value": "ability" }, "optional": false }, @@ -199278,7 +153596,7 @@ "kind": { "fieldType": { "type": "literal", - "value": "image" + "value": "drawing" }, "optional": false }, @@ -202538,7 +156856,7 @@ "kind": { "fieldType": { "type": "literal", - "value": "utility" + "value": "text" }, "optional": false }, @@ -202549,186 +156867,7 @@ "optional": false } } - } - ] - }, - "optional": false - }, - "publicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "revision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "sortIndex": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "strategyPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "updatedAt": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - } - }, - "optional": false - }, - "header": { - "fieldType": { - "type": "object", - "value": { - "createdAt": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "mapData": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "name": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "publicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "revision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "role": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "literal", - "value": "owner" - }, - { - "type": "literal", - "value": "editor" - }, - { - "type": "literal", - "value": "viewer" - } - ] - }, - "optional": false - }, - "themeOverridePalette": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "object", - "value": { - "base": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "detail": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "highlight": { - "fieldType": { - "type": "string" - }, - "optional": false - } - } - }, - { - "type": "null" - } - ] - }, - "optional": false - }, - "themeProfileId": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "optional": false - }, - "updatedAt": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - "optional": false - }, - "lineups": { - "fieldType": { - "type": "array", - "value": { - "type": "object", - "value": { - "createdAt": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "deleted": { - "fieldType": { - "type": "boolean" - }, - "optional": false - }, - "pagePublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "payload": { - "fieldType": { - "type": "union", - "value": [ + }, { "type": "object", "value": { @@ -205977,7 +160116,7 @@ "kind": { "fieldType": { "type": "literal", - "value": "lineupOrigin" + "value": "image" }, "optional": false }, @@ -206247,24 +160386,228 @@ } ] }, - "optional": false + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } - } - ] - }, - "optional": false + ] + }, + "optional": false + } } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -206433,20 +160776,224 @@ } } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -206819,20 +161366,428 @@ } } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -207142,15 +162097,321 @@ } } ] - } - }, - { - "keys": { - "type": "string" + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -207217,25 +162478,127 @@ } } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } - } - ] - }, - "optional": false + ] + }, + "optional": false + } } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -207608,25 +162971,654 @@ } } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } - } - ] - }, - "optional": false + ] + }, + "optional": false + } } - } - ] + ] + }, + "optional": false } + } + ] + }, + "optional": false + } + }, + "optional": false + }, + "kind": { + "fieldType": { + "type": "literal", + "value": "utility" + }, + "optional": false + }, + "payloadVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + } + ] + }, + "optional": false + }, + "publicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "revision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "sortIndex": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "updatedAt": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + } + }, + "optional": false + }, + "header": { + "fieldType": { + "type": "object", + "value": { + "createdAt": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "mapData": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "name": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "publicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "revision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "role": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "literal", + "value": "owner" + }, + { + "type": "literal", + "value": "editor" + }, + { + "type": "literal", + "value": "viewer" + } + ] + }, + "optional": false + }, + "themeOverridePalette": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "object", + "value": { + "base": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "detail": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "highlight": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "themeProfileId": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "updatedAt": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + "optional": false + }, + "lineups": { + "fieldType": { + "type": "array", + "value": { + "type": "object", + "value": { + "createdAt": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "deleted": { + "fieldType": { + "type": "boolean" + }, + "optional": false + }, + "pagePublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "payload": { + "fieldType": { + "type": "object", + "value": { + "data": { + "fieldType": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, { - "keys": { - "type": "string" + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -207718,67 +163710,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -207793,67 +163734,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -207895,142 +163785,40 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] }, @@ -208099,67 +163887,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -208174,67 +163911,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -208276,67 +163962,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -208351,67 +163986,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -208507,6 +164091,102 @@ "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -208523,9 +164203,167 @@ } ] }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -208541,33 +164379,136 @@ "type": "string" } ] - } + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] } @@ -208582,67 +164523,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -208684,67 +164574,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -208759,67 +164598,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -208888,67 +164676,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -208956,74 +164693,23 @@ { "keys": { "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] }, @@ -209065,67 +164751,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -209140,67 +164775,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -209218,114 +164802,15 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "lineupLanding" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + } }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -209698,59 +165183,365 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "boolean" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "number" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -209817,18 +165608,115 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -209845,92 +165733,41 @@ } ] }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false + } + } + ] } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + "type": "array", + "value": { "type": "union", "value": [ { @@ -209946,10 +165783,15 @@ "type": "string" } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -209965,178 +165807,25 @@ "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "optional": false } - ] - } + } + ] }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -210305,396 +165994,91 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false } - ] - } + } + ] }, - { - "keys": { - "type": "string" + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -210720,67 +166104,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] } @@ -210795,67 +166128,16 @@ "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] }, @@ -210863,118 +166145,15 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + } }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -211041,120 +166220,64 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false } - ] - } + } + ] + } + }, + { + "keys": { + "type": "string" }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + "type": "array", + "value": { "type": "union", "value": [ { @@ -211170,10 +166293,15 @@ "type": "string" } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -211189,173 +166317,20 @@ "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "optional": false } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + } + ] } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" }, { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -211422,18 +166397,115 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -211450,92 +166522,41 @@ } ] }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false + } + } + ] } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + "type": "array", + "value": { "type": "union", "value": [ { @@ -211551,10 +166572,15 @@ "type": "string" } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -211570,178 +166596,25 @@ "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "optional": false } - ] - } + } + ] }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false + } + } + ] } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -211910,606 +166783,46 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false } - ] - } + } + ] }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "lineupLink" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] }, "optional": false } - } + }, + "optional": false + }, + "kind": { + "fieldType": { + "type": "literal", + "value": "lineup" + }, + "optional": false + }, + "payloadVersion": { + "fieldType": { + "type": "number" + }, + "optional": false } - ] + } }, "optional": false }, diff --git a/convex/imageAssetLifecycle.test.ts b/convex/imageAssetLifecycle.test.ts index d6c919dc..7f88a0bf 100644 --- a/convex/imageAssetLifecycle.test.ts +++ b/convex/imageAssetLifecycle.test.ts @@ -11,7 +11,11 @@ import { markAssetReferencesReady } from "./lib/assetReferences"; import { CURRENT_CLOUD_PROTOCOL_VERSION } from "./lib/cloudProtocol"; import { PAGE_TRASH_RETENTION_MS } from "./lib/entities"; import schema from "./schema"; -import { insertElement, insertLineup } from "./testContent.helpers"; +import { + insertElement, + insertLineup, + lineupPayload, +} from "./testContent.helpers"; import { modules } from "./test.setup"; const ensureCurrentUser = makeFunctionReference<"mutation">( @@ -105,19 +109,6 @@ function imagePayload(assetPublicId: string) { }; } -function lineupPayload(assetPublicId: string) { - return { - kind: "lineupLink" as const, - payloadVersion: 1, - data: { - id: "remaining-lineup", - originId: "origin", - landingId: "landing", - images: [{ id: assetPublicId }], - }, - }; -} - function mockR2Deletes(statuses: number[] = [204]) { let callIndex = 0; const fetchMock = vi.fn( @@ -246,12 +237,14 @@ describe("image asset lifecycle", () => { }); } await insertLineup(ctx, { - publicId: "lineupLink:remaining-lineup", + publicId: "remaining-lineup", strategyId: strategy._id, pageId: pageBId, - payloadKind: "lineupLink", + payloadKind: "lineup", payloadVersion: 1, - payload: lineupPayload("still-used"), + payload: lineupPayload("remaining-lineup", { + images: [{ id: "still-used" }], + }), sortIndex: 0, revision: 1, deleted: false, @@ -259,21 +252,14 @@ describe("image asset lifecycle", () => { updatedAt: now, }); await insertLineup(ctx, { - publicId: "lineupLink:remaining-link", + publicId: "remaining-link", strategyId: strategy._id, pageId: pageBId, - payloadKind: "lineupLink", + payloadKind: "lineup", payloadVersion: 1, - payload: { - kind: "lineupLink", - payloadVersion: 1, - data: { - id: "remaining-link", - originId: "origin", - landingId: "landing", - images: [{ id: "still-used-by-link", fileExtension: ".png" }], - }, - }, + payload: lineupPayload("remaining-link", { + images: [{ id: "still-used-by-link", fileExtension: ".png" }], + }), sortIndex: 1, revision: 1, deleted: false, diff --git a/convex/lib/cloudProtocol.ts b/convex/lib/cloudProtocol.ts index a820eea8..b3ad5b28 100644 --- a/convex/lib/cloudProtocol.ts +++ b/convex/lib/cloudProtocol.ts @@ -8,7 +8,11 @@ import { v } from "convex/values"; // public query that returns element or lineup payloads, and the shell the // editor opens with, takes the protocol too: a client that cannot read a // row must not see one. -export const CURRENT_CLOUD_PROTOCOL_VERSION = 4; +// 5: a lineup is one row of kind "lineup" carrying its origin and landing, +// replacing the origin, landing and link rows of 4. A client on 4 cannot +// read the new rows and writes rows the server no longer takes, so it is +// refused until it reloads into 5. +export const CURRENT_CLOUD_PROTOCOL_VERSION = 5; export const MAX_CLOUD_OPERATION_BYTES = 900 * 1024; export const MAX_CLOUD_ARRAY_ENTRIES = 8_000; export const CLOUD_OPERATION_TOO_LARGE_MESSAGE = diff --git a/convex/lib/errors.ts b/convex/lib/errors.ts index 534b9299..8d3897f4 100644 --- a/convex/lib/errors.ts +++ b/convex/lib/errors.ts @@ -18,8 +18,6 @@ export const errorCodes = [ "INVALID_PAYLOAD", "INVITE_EXPIRED", "INVITE_REVOKED", - "LINEUP_END_IN_USE", - "LINEUP_LINK_END_MISSING", "LINEUP_PAGE_MISMATCH", "LINEUP_STRATEGY_MISMATCH", "MISSING_ADD_ELEMENT_ARGS", diff --git a/convex/lib/imageAssets.ts b/convex/lib/imageAssets.ts index ed233310..0ac94a57 100644 --- a/convex/lib/imageAssets.ts +++ b/convex/lib/imageAssets.ts @@ -52,37 +52,23 @@ export function collectAssetIdFromElementPayload( return typeof payload.data.id === "string" ? payload.data.id : null; } +/// The images a lineup shows: data.images[*].id. Its origin and landing +/// hold none. export function collectAssetIdsFromLineupPayload( payload: Doc<"lineups">["payload"], ): Set { const assetIds = new Set(); - - const addImages = (rawImages: unknown) => { - if (!Array.isArray(rawImages)) { - return; - } - for (const image of rawImages) { - if ( - typeof image === "object" && - image !== null && - typeof (image as { id?: unknown }).id === "string" - ) { - assetIds.add((image as { id: string }).id); - } + const images = payload.data.images; + if (!Array.isArray(images)) return assetIds; + for (const image of images) { + if ( + typeof image === "object" && + image !== null && + typeof (image as { id?: unknown }).id === "string" + ) { + assetIds.add((image as { id: string }).id); } - }; - - switch (payload.kind) { - case "lineupLink": - // A link holds its images: data.images[*].id. Origins and landings - // hold none. - addImages(payload.data.images); - break; - case "lineupOrigin": - case "lineupLanding": - break; } - return assetIds; } diff --git a/convex/lib/payloadValidators.ts b/convex/lib/payloadValidators.ts index c92efdcc..ed260155 100644 --- a/convex/lib/payloadValidators.ts +++ b/convex/lib/payloadValidators.ts @@ -114,29 +114,14 @@ export const elementPayloadValidator = v.union( }), ); -// A page's lineups are a graph, stored one row per entity: an origin (the -// agent), a landing (the ability) and a link between them (name, video, -// notes, images). Several links may share one landing. -export const lineupPayloadKindValidator = v.union( - v.literal("lineupOrigin"), - v.literal("lineupLanding"), - v.literal("lineupLink"), -); +// A lineup is one row: its details (name, video, notes, images) and whole +// copies of its origin (the agent) and landing (the ability). Lineups that +// share a spot each carry their own copy of it, and the client draws those +// copies as one spot again (see cloud_lineup_rows.dart). +export const lineupPayloadKindValidator = v.literal("lineup"); -export const lineupPayloadValidator = v.union( - v.object({ - kind: v.literal("lineupOrigin"), - payloadVersion: v.number(), - data: cloudJsonObjectValidator, - }), - v.object({ - kind: v.literal("lineupLanding"), - payloadVersion: v.number(), - data: cloudJsonObjectValidator, - }), - v.object({ - kind: v.literal("lineupLink"), - payloadVersion: v.number(), - data: cloudJsonObjectValidator, - }), -); +export const lineupPayloadValidator = v.object({ + kind: v.literal("lineup"), + payloadVersion: v.number(), + data: cloudJsonObjectValidator, +}); diff --git a/convex/lib/strategyAgentSummary.ts b/convex/lib/strategyAgentSummary.ts index 05f12457..79d4f73a 100644 --- a/convex/lib/strategyAgentSummary.ts +++ b/convex/lib/strategyAgentSummary.ts @@ -1,45 +1,42 @@ import type { Doc, Id } from "../_generated/dataModel"; import type { MutationCtx, QueryCtx } from "../_generated/server"; -/// Reads the agent type of one agent element or lineup payload. The client -/// stores the agent enum name under `type` (an element) or under -/// `agent.type` (a lineup origin); anything else, -/// landings and links included, is not an agent. +function asRecord(value: unknown): Record | null { + return typeof value === "object" && value !== null + ? (value as Record) + : null; +} + +/// Reads the agent type the client stores as the enum name under `type`, as +/// an agent element's data and a lineup origin's agent both do. function agentTypeOf(data: unknown): string | null { - if (typeof data !== "object" || data === null) return null; - const record = data as Record; - const direct = record.type; - if (typeof direct === "string" && direct.length > 0) return direct; - const agent = record.agent; - if (typeof agent === "object" && agent !== null) { - const nested = (agent as Record).type; - if (typeof nested === "string" && nested.length > 0) return nested; - } - return null; + const type = asRecord(data)?.type; + return typeof type === "string" && type.length > 0 ? type : null; } /// Recomputes which agents a strategy uses, from its live agent elements and -/// lineup origins on pages not in the trash, and stores the answer in its -/// own row. Content ops never +/// the origins of its live lineups on pages not in the trash, and stores the +/// answer in its own row. Content ops never /// touch the strategy row itself; the summary is derived data that the /// folder tree reads without scanning elements. export async function refreshStrategyAgentSummary( ctx: MutationCtx, strategyId: Id<"strategies">, ): Promise { - // Only agent elements and lineup origins carry an agent; reading just - // those keeps this cheap however large the strategy's other content is - // (it runs after every batch of content ops). + // Only agent elements and live lineups carry an agent; reading just those + // keeps this cheap however large the strategy's other content is (it runs + // after every batch of content ops). A lineup row holds its images' ids, + // never their bytes. const agents = await ctx.db .query("elements") .withIndex("by_strategyId_and_elementType", (q) => q.eq("strategyId", strategyId).eq("elementType", "agent"), ) .collect(); - const origins = await ctx.db + const lineups = await ctx.db .query("lineups") - .withIndex("by_strategyId_and_payloadKind", (q) => - q.eq("strategyId", strategyId).eq("payloadKind", "lineupOrigin"), + .withIndex("by_strategyId_and_deleted", (q) => + q.eq("strategyId", strategyId).eq("deleted", false), ) .collect(); const trashedPageIds = new Set( @@ -57,15 +54,16 @@ export async function refreshStrategyAgentSummary( await storeStrategyAgentSummary( ctx, strategyId, - agentTypesOf(agents.filter(onLivePage), origins.filter(onLivePage)), + agentTypesOf(agents.filter(onLivePage), lineups.filter(onLivePage)), ); } /// The agents that live content uses, most used first. Takes any elements -/// and lineup rows; only live agent elements and lineup origins count. +/// and lineup rows; live agent elements count, and each origin of a live +/// lineup once per page, however many lineups share it. export function agentTypesOf( elements: Pick, "deleted" | "elementType" | "payload">[], - lineups: Pick, "deleted" | "payloadKind" | "payload">[], + lineups: Pick, "deleted" | "pageId" | "payload">[], ): string[] { const counts = new Map(); const bump = (type: string | null) => { @@ -76,9 +74,14 @@ export function agentTypesOf( if (element.deleted || element.elementType !== "agent") continue; bump(agentTypeOf(element.payload.data)); } + const countedOrigins = new Set(); for (const lineup of lineups) { - if (lineup.deleted || lineup.payloadKind !== "lineupOrigin") continue; - bump(agentTypeOf(lineup.payload.data)); + if (lineup.deleted) continue; + const origin = asRecord(lineup.payload.data.origin); + const originKey = `${lineup.pageId}:${String(origin?.id)}`; + if (origin === null || countedOrigins.has(originKey)) continue; + countedOrigins.add(originKey); + bump(agentTypeOf(origin.agent)); } return [...counts.entries()] .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])) diff --git a/convex/lineupGraph.test.ts b/convex/lineupGraph.test.ts deleted file mode 100644 index 37d82c18..00000000 --- a/convex/lineupGraph.test.ts +++ /dev/null @@ -1,1014 +0,0 @@ -import { - convexTest, - type TestConvexForDataModel, - type TestConvexForDataModelAndIdentity, -} from "convex-test"; -import { makeFunctionReference } from "convex/server"; -import { beforeAll, describe, expect, test } from "vitest"; -import type { DataModel } from "./_generated/dataModel"; -import { CURRENT_CLOUD_PROTOCOL_VERSION } from "./lib/cloudProtocol"; -import schema from "./schema"; -import { modules } from "./test.setup"; - -const ensureCurrentUser = makeFunctionReference<"mutation">( - "users:ensureCurrentUser", -); -const createStrategy = makeFunctionReference<"mutation">( - "strategies:createWithInitialPage", -); -const createShare = makeFunctionReference<"mutation">("shares:create"); -const redeemShare = makeFunctionReference<"mutation">("shares:redeem"); -const addPage = makeFunctionReference<"mutation">("pages:add"); -const applyBatch = makeFunctionReference<"mutation">("ops:applyBatch"); -const getPageSnapshot = makeFunctionReference<"query">("page:getSnapshot"); -const getFullSnapshot = makeFunctionReference<"query">( - "strategy:getFullSnapshot", -); - -type Harness = TestConvexForDataModel; -type RootHarness = TestConvexForDataModelAndIdentity; -type Result = Record; -type LineupRow = { - publicId: string; - payload: { kind: string; data: Record }; - revision: number; - deleted: boolean; -}; - -const strategyPublicId = "lineup-graph-strategy"; -const pagePublicId = "lineup-graph-page"; - -function identity(subject: string) { - return { - issuer: "https://lineup-graph.test", - subject, - tokenIdentifier: `lineup-graph|${subject}`, - name: subject, - }; -} - -async function createHarness(): Promise<{ - t: RootHarness; - owner: Harness; - editor: Harness; - viewer: Harness; -}> { - const t = convexTest(schema, modules); - const owner = t.withIdentity(identity("owner")); - const editor = t.withIdentity(identity("editor")); - const viewer = t.withIdentity(identity("viewer")); - for (const user of [owner, editor, viewer]) { - await user.mutation(ensureCurrentUser, { - clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, - }); - } - await owner.mutation(createStrategy, { - clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, - publicId: strategyPublicId, - name: "Lineup graph", - mapData: "ascent", - initialPagePublicId: pagePublicId, - initialPageName: "Page 1", - initialPageIsAttack: true, - }); - for (const [user, role] of [ - [editor, "editor"], - [viewer, "viewer"], - ] as const) { - await owner.mutation(createShare, { - clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, - targetType: "strategy", - targetPublicId: strategyPublicId, - token: `${role}-token`, - role, - }); - await user.mutation(redeemShare, { - clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, - token: `${role}-token`, - }); - } - return { t, owner, editor, viewer }; -} - -function origin(id: string, agentType = "sova") { - return { - key: `lineupOrigin:${id}`, - payload: { - kind: "lineupOrigin" as const, - payloadVersion: 1, - data: { id, agent: { id: `agent-${id}`, type: agentType, lineUpID: id } }, - }, - }; -} - -function landing(id: string) { - return { - key: `lineupLanding:${id}`, - payload: { - kind: "lineupLanding" as const, - payloadVersion: 1, - data: { id, ability: { id: `ability-${id}`, lineUpID: id } }, - }, - }; -} - -function link( - id: string, - originId: string, - landingId: string, - details: Record = {}, -) { - return { - key: `lineupLink:${id}`, - payload: { - kind: "lineupLink" as const, - payloadVersion: 1, - data: { - id, - originId, - landingId, - name: "", - youtubeLink: "", - notes: "", - images: [], - ...details, - }, - }, - }; -} - -type Row = ReturnType; - -function addOp(row: Row, sortIndex: number, page = pagePublicId) { - return { - opId: `add-${row.key}`, - type: "lineup.add", - lineupPublicId: row.key, - pagePublicId: page, - payload: row.payload, - sortIndex, - }; -} - -function patchOp(opId: string, row: Row, expectedLineupRevision: number) { - return { - opId, - type: "lineup.patch", - lineupPublicId: row.key, - pagePublicId, - payload: row.payload, - expectedLineupRevision, - }; -} - -async function apply( - user: Harness, - clientId: string, - ops: Array>, - strategy = strategyPublicId, - checks: { - checkLineupLinkEnds?: boolean; - checkLineupEndDeletes?: boolean; - } = {}, -): Promise { - const response = (await user.mutation(applyBatch, { - strategyPublicId: strategy, - clientId, - clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, - ops, - ...checks, - })) as { results: Result[] }; - return response.results; -} - -/** - * [apply] as a current client sends it: link ends are checked, and so are - * deletes of an end a live link names. - */ -async function applyChecked( - user: Harness, - clientId: string, - ops: Array>, -): Promise { - return await apply(user, clientId, ops, strategyPublicId, { - checkLineupLinkEnds: true, - checkLineupEndDeletes: true, - }); -} - -async function pageLineups( - user: Harness, - strategy = strategyPublicId, - page = pagePublicId, -): Promise { - const snapshot = (await user.query(getPageSnapshot, { - clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, - strategyPublicId: strategy, - pagePublicId: page, - })) as { lineups: LineupRow[] }; - return snapshot.lineups; -} - -/** A second strategy owned by [owner], with one page. */ -async function createCopy(owner: Harness, publicId: string, page: string) { - await owner.mutation(createStrategy, { - clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, - publicId, - name: "Copy", - mapData: "ascent", - initialPagePublicId: page, - initialPageName: "Page 1", - initialPageIsAttack: true, - }); -} - -function byKey(rows: LineupRow[]): Map { - return new Map(rows.map((row) => [row.publicId, row])); -} - -beforeAll(() => { - // Page snapshots serialize asset URLs from the R2 public base. - process.env.R2_ACCOUNT_ID = "lineup-graph-account"; - process.env.R2_BUCKET = "lineup-graph-bucket"; - process.env.R2_ACCESS_KEY_ID = "lineup-graph-access-key"; - process.env.R2_SECRET_ACCESS_KEY = "lineup-graph-secret"; - process.env.R2_PUBLIC_BASE_URL = "https://assets.lineup-graph.test"; - process.env.R2_S3_ENDPOINT = "https://lineup-graph.r2.test"; -}); - -/** Two origins aiming at one shared landing, each link named. */ -const fanIn = [ - origin("origin-a"), - origin("origin-b"), - landing("shared"), - link("link-a", "origin-a", "shared", { name: "From heaven" }), - link("link-b", "origin-b", "shared", { name: "From mid" }), -]; - -describe("lineup graph rows", () => { - test("a fan-in lineup round-trips with its shared landing and link names", async () => { - const { owner } = await createHarness(); - const results = await apply( - owner, - "owner-client", - fanIn.map((row, index) => addOp(row, index)), - ); - expect(results.map((result) => result.status)).toEqual( - fanIn.map(() => "applied"), - ); - - const rows = await pageLineups(owner); - expect(rows.map((row) => row.payload.kind)).toEqual([ - "lineupOrigin", - "lineupOrigin", - "lineupLanding", - "lineupLink", - "lineupLink", - ]); - const links = rows.filter((row) => row.payload.kind === "lineupLink"); - expect(links.map((row) => row.payload.data)).toEqual([ - expect.objectContaining({ - originId: "origin-a", - landingId: "shared", - name: "From heaven", - }), - expect.objectContaining({ - originId: "origin-b", - landingId: "shared", - name: "From mid", - }), - ]); - const landings = rows.filter((row) => row.payload.kind === "lineupLanding"); - expect(landings).toHaveLength(1); - - const full = (await owner.query(getFullSnapshot, { - clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, - strategyPublicId, - })) as { lineups: LineupRow[] }; - expect(full.lineups.map((row) => row.publicId)).toEqual( - fanIn.map((row) => row.key), - ); - }); - - test("a landing and a link may share an entity id", async () => { - const { owner } = await createHarness(); - const rows = [ - origin("o"), - landing("same-id"), - link("same-id", "o", "same-id"), - ]; - const results = await apply( - owner, - "owner-client", - rows.map((row, index) => addOp(row, index)), - ); - expect(results.map((result) => result.status)).toEqual([ - "applied", - "applied", - "applied", - ]); - expect((await pageLineups(owner)).map((row) => row.publicId)).toEqual([ - "lineupOrigin:o", - "lineupLanding:same-id", - "lineupLink:same-id", - ]); - }); - - test("two users editing different links of one origin both land", async () => { - const { owner, editor } = await createHarness(); - const rows = [ - origin("o"), - landing("l1"), - landing("l2"), - link("k1", "o", "l1"), - link("k2", "o", "l2"), - ]; - await apply( - owner, - "owner-client", - rows.map((row, index) => addOp(row, index)), - ); - - // Both edit from the same loaded revision (1) of their own link. - const ownerResults = await apply(owner, "owner-client", [ - patchOp("owner-renames-k1", link("k1", "o", "l1", { name: "Owner" }), 1), - ]); - const editorResults = await apply(editor, "editor-client", [ - patchOp( - "editor-notes-k2", - link("k2", "o", "l2", { notes: "Editor notes" }), - 1, - ), - ]); - expect(ownerResults[0]).toMatchObject({ - status: "applied", - appliedRevision: 2, - }); - expect(editorResults[0]).toMatchObject({ - status: "applied", - appliedRevision: 2, - }); - - const after = byKey(await pageLineups(owner)); - expect(after.get("lineupLink:k1")?.payload.data).toMatchObject({ - name: "Owner", - notes: "", - }); - expect(after.get("lineupLink:k2")?.payload.data).toMatchObject({ - name: "", - notes: "Editor notes", - }); - // Neither edit touched the origin or the other link's revision. - expect(after.get("lineupOrigin:o")?.revision).toBe(1); - - // The same link edited from a stale revision is a conflict, not a - // silent overwrite. - const stale = await apply(editor, "editor-client", [ - patchOp("editor-renames-k1", link("k1", "o", "l1", { name: "Stale" }), 1), - ]); - expect(stale[0]).toMatchObject({ - status: "rejected", - reason: "revision_mismatch", - current: { type: "lineup", revision: 2 }, - }); - }); - - test("deleting one link leaves the shared landing and the other link", async () => { - const { owner } = await createHarness(); - await apply( - owner, - "owner-client", - fanIn.map((row, index) => addOp(row, index)), - ); - const results = await apply(owner, "owner-client", [ - { - opId: "delete-link-a", - type: "lineup.delete", - lineupPublicId: "lineupLink:link-a", - pagePublicId, - expectedLineupRevision: 1, - }, - { - opId: "delete-origin-a", - type: "lineup.delete", - lineupPublicId: "lineupOrigin:origin-a", - pagePublicId, - expectedLineupRevision: 1, - }, - ]); - expect(results.map((result) => result.status)).toEqual([ - "applied", - "applied", - ]); - const live = (await pageLineups(owner)) - .filter((row) => !row.deleted) - .map((row) => row.publicId); - expect(live).toEqual([ - "lineupOrigin:origin-b", - "lineupLanding:shared", - "lineupLink:link-b", - ]); - }); - - test("graph rows that could not be drawn are refused", async () => { - const { owner } = await createHarness(); - const mismatchedKey = { - ...addOp(link("k", "o", "l"), 0), - lineupPublicId: "lineupLink:other", - }; - const unqualifiedKey = { ...addOp(origin("o"), 0), lineupPublicId: "o" }; - const originWithoutAgent = { - ...addOp(origin("o2"), 0), - payload: { kind: "lineupOrigin", payloadVersion: 1, data: { id: "o2" } }, - }; - const linkWithoutLanding = { - ...addOp(link("k2", "o", "l"), 0), - payload: { - kind: "lineupLink", - payloadVersion: 1, - data: { id: "k2", originId: "o" }, - }, - }; - const results = await apply(owner, "owner-client", [ - mismatchedKey, - unqualifiedKey, - originWithoutAgent, - linkWithoutLanding, - ]); - for (const result of results) { - expect(result).toMatchObject({ - status: "failed", - code: "INVALID_LINEUP_PAYLOAD_DATA", - }); - } - expect(await pageLineups(owner)).toEqual([]); - }); - - test("a viewer cannot write lineup rows", async () => { - const { owner, viewer } = await createHarness(); - await apply(owner, "owner-client", [addOp(origin("o"), 0)]); - await expect( - apply(viewer, "viewer-client", [addOp(landing("l"), 1)]), - ).rejects.toThrow("Forbidden"); - await expect( - apply(viewer, "viewer-client", [ - patchOp("viewer-moves-origin", origin("o", "jett"), 1), - ]), - ).rejects.toThrow("Forbidden"); - // Viewers still read them. - expect((await pageLineups(viewer)).map((row) => row.publicId)).toEqual([ - "lineupOrigin:o", - ]); - }); - - test("images on a link are referenced assets of the page", async () => { - const { t, owner } = await createHarness(); - await t.run(async (ctx) => { - const strategy = await ctx.db - .query("strategies") - .withIndex("by_publicId", (q) => q.eq("publicId", strategyPublicId)) - .first(); - const now = Date.now(); - await ctx.db.insert("imageAssets", { - publicId: "link-image", - provider: "r2", - strategyId: strategy!._id, - objectKey: "tests/link-image.png", - uploadStatus: "active", - fileExtension: ".png", - createdAt: now, - updatedAt: now, - }); - }); - await apply(owner, "owner-client", [ - addOp(origin("o"), 0), - addOp(landing("l"), 1), - addOp( - link("k", "o", "l", { - images: [{ id: "link-image", fileExtension: ".png" }], - }), - 2, - ), - ]); - const snapshot = (await owner.query(getPageSnapshot, { - clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, - strategyPublicId, - pagePublicId, - })) as { assets: Array<{ publicId: string }> }; - expect(snapshot.assets.map((asset) => asset.publicId)).toEqual([ - "link-image", - ]); - }); -}); - -describe("lineup row keys belong to their strategy", () => { - /** A legacy group as a client wrote it before the graph synced. */ - test("strategies uploaded from a local duplicate keep the same row keys", async () => { - const { owner } = await createHarness(); - const copyId = "local-duplicate"; - const copyPage = "local-duplicate-page"; - await createCopy(owner, copyId, copyPage); - // A strategy duplicated on the device keeps every lineup id; each upload - // keeps them too (ids only need to be unique within a strategy). - for (const [strategy, page] of [ - [strategyPublicId, pagePublicId], - [copyId, copyPage], - ] as const) { - const results = await apply( - owner, - `upload-${strategy}`, - fanIn.map((row, index) => addOp(row, index, page)), - strategy, - ); - expect(results.map((result) => result.status)).toEqual( - fanIn.map(() => "applied"), - ); - } - // Editing one copy leaves the other untouched. - await apply( - owner, - "edit-copy", - [ - { - ...patchOp( - "rename-in-copy", - link("link-a", "origin-a", "shared", { name: "Copy name" }), - 1, - ), - pagePublicId: copyPage, - }, - ], - copyId, - ); - const original = byKey(await pageLineups(owner)); - const copy = byKey(await pageLineups(owner, copyId, copyPage)); - expect(original.get("lineupLink:link-a")?.payload.data.name).toBe( - "From heaven", - ); - expect(copy.get("lineupLink:link-a")?.payload.data.name).toBe("Copy name"); - }); - - test("a lineup row is never moved to another page by a patch", async () => { - const { owner } = await createHarness(); - const secondPage = "lineup-graph-page-2"; - await owner.mutation(addPage, { - clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, - strategyPublicId, - expectedRevision: 0, - pagePublicId: secondPage, - name: "Page 2", - sortIndex: 1, - isAttack: false, - }); - await apply(owner, "page-1-client", [addOp(link("k", "o", "l"), 0)]); - - // Another page wants the same key: the add is refused, and "Keep mine" - // would turn it into a patch naming the other page. - const clashing = link("k", "o2", "l2", { name: "Page 2 lineup" }); - const added = await apply(owner, "page-2-client", [ - addOp(clashing, 0, secondPage), - ]); - expect(added[0]).toMatchObject({ - status: "rejected", - reason: "already_exists", - }); - const moved = await apply(owner, "page-2-client", [ - { ...patchOp("keep-mine", clashing, 1), pagePublicId: secondPage }, - ]); - expect(moved[0]).toMatchObject({ - status: "failed", - code: "LINEUP_PAGE_MISMATCH", - }); - - // The row stays on its page, untouched. - const first = await pageLineups(owner); - expect(first.map((row) => row.publicId)).toEqual(["lineupLink:k"]); - expect(first[0]!.payload.data).toMatchObject({ originId: "o" }); - expect(await pageLineups(owner, strategyPublicId, secondPage)).toEqual([]); - }); - - test("the same row added twice with a different order is one add", async () => { - const { owner, editor } = await createHarness(); - const first = await apply(owner, "owner-client", [addOp(origin("o"), 3)]); - const second = await apply(editor, "editor-client", [addOp(origin("o"), 7)]); - expect(first[0]).toMatchObject({ status: "applied" }); - expect(second[0]).toMatchObject({ status: "noop" }); - const conflicting = await apply(editor, "editor-client", [ - { ...addOp(origin("o", "jett"), 7), opId: "different-content" }, - ]); - expect(conflicting[0]).toMatchObject({ - status: "rejected", - reason: "already_exists", - }); - }); -}); - -function deleteOp(opId: string, key: string, expectedLineupRevision: number) { - return { - opId, - type: "lineup.delete", - lineupPublicId: key, - pagePublicId, - expectedLineupRevision, - }; -} - -describe("a link needs both of its ends", () => { - test("a link placed from an origin a teammate deleted is refused", async () => { - const { owner, editor } = await createHarness(); - await apply( - owner, - "owner-client", - [origin("a"), landing("a"), link("a", "a", "a")].map((row, index) => - addOp(row, index), - ), - ); - // The editor deletes lineup "a" while the owner is placing a new - // lineup from its origin. - await apply(editor, "editor-client", [ - deleteOp("delete-link-a", "lineupLink:a", 1), - deleteOp("delete-landing-a", "lineupLanding:a", 1), - deleteOp("delete-origin-a", "lineupOrigin:a", 1), - ]); - - // The owner's client still had origin "a" as its base, so it sends only - // the new landing and link. - const results = await applyChecked(owner, "owner-client", [ - addOp(landing("b"), 3), - addOp(link("b", "a", "b"), 4), - ]); - expect(results[0]).toMatchObject({ status: "applied" }); - expect(results[1]).toMatchObject({ - status: "failed", - code: "LINEUP_LINK_END_MISSING", - }); - const rows = byKey(await pageLineups(owner)); - expect(rows.has("lineupLink:b")).toBe(false); - }); - - test("an older client's link a batch ahead of its origin still lands", async () => { - const { owner } = await createHarness(); - // An outbox reloaded after a restart lists its rows by key (landing, - // link, origin) and may cut the batch before the origin. - const first = await apply(owner, "old-client", [ - addOp(landing("l"), 0), - addOp(link("k", "o", "l"), 1), - ]); - const second = await apply(owner, "old-client", [addOp(origin("o"), 2)]); - expect([...first, ...second].map((result) => result.status)).toEqual([ - "applied", - "applied", - "applied", - ]); - // The same batch from a current client is refused. - const checked = await applyChecked(owner, "new-client", [ - addOp(link("k2", "o2", "l"), 3), - ]); - expect(checked[0]).toMatchObject({ - status: "failed", - code: "LINEUP_LINK_END_MISSING", - }); - }); - - test("an origin restored earlier in the same batch lets the link land", async () => { - const { owner, editor } = await createHarness(); - await apply( - owner, - "owner-client", - [origin("a"), landing("a"), link("a", "a", "a")].map((row, index) => - addOp(row, index), - ), - ); - await apply(editor, "editor-client", [ - deleteOp("delete-link-a", "lineupLink:a", 1), - deleteOp("delete-landing-a", "lineupLanding:a", 1), - deleteOp("delete-origin-a", "lineupOrigin:a", 1), - ]); - - const results = await applyChecked(owner, "owner-client", [ - { - ...addOp(origin("a"), 0), - opId: "restore-origin-a", - expectedLineupRevision: 2, - }, - addOp(landing("b"), 3), - addOp(link("b", "a", "b"), 4), - ]); - expect(results.map((result) => result.status)).toEqual([ - "applied", - "applied", - "applied", - ]); - const live = (await pageLineups(owner)) - .filter((row) => !row.deleted) - .map((row) => row.publicId); - expect(live).toEqual([ - "lineupOrigin:a", - "lineupLanding:b", - "lineupLink:b", - ]); - }); - - test("editing a link whose landing a teammate deleted is refused", async () => { - const { owner, editor } = await createHarness(); - await apply( - owner, - "owner-client", - [origin("o"), landing("l"), link("k", "o", "l")].map((row, index) => - addOp(row, index), - ), - ); - await apply(editor, "editor-client", [ - deleteOp("delete-landing", "lineupLanding:l", 1), - ]); - - const results = await applyChecked(owner, "owner-client", [ - patchOp("rename-k", link("k", "o", "l", { name: "Renamed" }), 1), - ]); - expect(results[0]).toMatchObject({ - status: "failed", - code: "LINEUP_LINK_END_MISSING", - }); - const row = byKey(await pageLineups(owner)).get("lineupLink:k"); - expect(row).toMatchObject({ revision: 1 }); - expect(row!.payload.data).toMatchObject({ name: "" }); - }); - - test("a link cannot use an end that lives on another page", async () => { - const { owner } = await createHarness(); - const secondPage = "lineup-graph-page-2"; - await owner.mutation(addPage, { - clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, - strategyPublicId, - expectedRevision: 0, - pagePublicId: secondPage, - name: "Page 2", - sortIndex: 1, - isAttack: false, - }); - await apply(owner, "owner-client", [ - addOp(origin("o"), 0), - addOp(landing("l"), 1), - ]); - - const results = await applyChecked(owner, "owner-client", [ - addOp(link("k", "o", "l"), 0, secondPage), - ]); - expect(results[0]).toMatchObject({ - status: "failed", - code: "LINEUP_LINK_END_MISSING", - }); - }); - - test("a link already stored without its origin still loads and deletes", async () => { - const { t, owner } = await createHarness(); - // Written before the server checked link ends. - await t.run(async (ctx) => { - const strategy = await ctx.db - .query("strategies") - .withIndex("by_publicId", (q) => q.eq("publicId", strategyPublicId)) - .unique(); - const page = await ctx.db - .query("pages") - .withIndex("by_publicId", (q) => q.eq("publicId", pagePublicId)) - .unique(); - const orphan = link("orphan", "gone", "gone"); - const now = Date.now(); - await ctx.db.insert("lineups", { - publicId: orphan.key, - strategyId: strategy!._id, - pageId: page!._id, - payloadKind: orphan.payload.kind, - payloadVersion: orphan.payload.payloadVersion, - payload: orphan.payload, - sortIndex: 0, - revision: 1, - deleted: false, - createdAt: now, - updatedAt: now, - }); - }); - - expect((await pageLineups(owner)).map((row) => row.publicId)).toEqual([ - "lineupLink:orphan", - ]); - const full = (await owner.query(getFullSnapshot, { - clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, - strategyPublicId, - })) as { lineups: LineupRow[] }; - expect(full.lineups.map((row) => row.publicId)).toEqual([ - "lineupLink:orphan", - ]); - const results = await applyChecked(owner, "owner-client", [ - deleteOp("delete-orphan", "lineupLink:orphan", 1), - ]); - expect(results[0]).toMatchObject({ status: "applied" }); - }); -}); - -describe("an origin or landing a live link names is not deleted", () => { - test("deleting an origin a teammate's new link uses is refused", async () => { - const { owner, editor } = await createHarness(); - await apply( - owner, - "owner-client", - [origin("a"), landing("a"), link("a", "a", "a")].map((row, index) => - addOp(row, index), - ), - ); - // The owner places a second lineup from origin "a", and it lands first. - await applyChecked(owner, "owner-client", [ - addOp(landing("b"), 3), - addOp(link("b", "a", "b"), 4), - ]); - - // The editor never saw lineup "b" and deletes lineup "a", which on - // their canvas takes origin "a" with it. - const results = await applyChecked(editor, "editor-client", [ - deleteOp("delete-link-a", "lineupLink:a", 1), - deleteOp("delete-landing-a", "lineupLanding:a", 1), - deleteOp("delete-origin-a", "lineupOrigin:a", 1), - ]); - expect(results.map((result) => result.status)).toEqual([ - "applied", - "applied", - "failed", - ]); - expect(results[2]).toMatchObject({ code: "LINEUP_END_IN_USE" }); - - const rows = byKey(await pageLineups(owner)); - expect(rows.get("lineupOrigin:a")).toMatchObject({ - deleted: false, - revision: 1, - }); - expect(rows.get("lineupLink:b")).toMatchObject({ deleted: false }); - expect(rows.get("lineupLink:a")).toMatchObject({ deleted: true }); - }); - - test("deleting a landing a live link uses is refused", async () => { - const { owner, editor } = await createHarness(); - await apply( - owner, - "owner-client", - [origin("o"), landing("l"), link("k", "o", "l")].map((row, index) => - addOp(row, index), - ), - ); - const results = await applyChecked(editor, "editor-client", [ - deleteOp("delete-landing", "lineupLanding:l", 1), - ]); - expect(results[0]).toMatchObject({ - status: "failed", - code: "LINEUP_END_IN_USE", - }); - const rows = byKey(await pageLineups(owner)); - expect(rows.get("lineupLanding:l")).toMatchObject({ deleted: false }); - }); - - test("a whole lineup deletes when its link goes first", async () => { - const { owner } = await createHarness(); - await apply( - owner, - "owner-client", - [origin("o"), landing("l"), link("k", "o", "l")].map((row, index) => - addOp(row, index), - ), - ); - const results = await applyChecked(owner, "owner-client", [ - deleteOp("delete-link", "lineupLink:k", 1), - deleteOp("delete-origin", "lineupOrigin:o", 1), - deleteOp("delete-landing", "lineupLanding:l", 1), - ]); - expect(results.map((result) => result.status)).toEqual([ - "applied", - "applied", - "applied", - ]); - }); - - test("a link on another page does not hold an end", async () => { - const { owner } = await createHarness(); - const secondPage = "lineup-graph-page-2"; - await owner.mutation(addPage, { - clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, - strategyPublicId, - expectedRevision: 0, - pagePublicId: secondPage, - name: "Page 2", - sortIndex: 1, - isAttack: false, - }); - // A link on page 2 names origin "o", which lives on page 1. Hydration - // reads each page on its own, so that link is never drawn from it. - await apply(owner, "owner-client", [ - addOp(origin("o"), 0), - addOp(landing("l2"), 1, secondPage), - addOp(link("k2", "o", "l2"), 2, secondPage), - ]); - const results = await applyChecked(owner, "owner-client", [ - deleteOp("delete-origin", "lineupOrigin:o", 1), - ]); - expect(results[0]).toMatchObject({ status: "applied" }); - }); - - test("a batch sees its own link writes after reading the page", async () => { - const { owner } = await createHarness(); - await apply( - owner, - "owner-client", - [ - origin("o"), - landing("l"), - link("k", "o", "l"), - origin("o2"), - landing("l2"), - landing("spare"), - ].map((row, index) => addOp(row, index)), - ); - const results = await applyChecked(owner, "owner-client", [ - // No link names this landing; checking it reads the page's links. - deleteOp("delete-spare", "lineupLanding:spare", 1), - // A link added after that read holds its origin... - addOp(link("k2", "o2", "l2"), 6), - deleteOp("delete-origin-o2", "lineupOrigin:o2", 1), - // ...and a link deleted after it no longer holds its ends. - deleteOp("delete-link-k", "lineupLink:k", 1), - deleteOp("delete-origin-o", "lineupOrigin:o", 1), - deleteOp("delete-landing-l", "lineupLanding:l", 1), - ]); - expect(results.map((result) => result.status)).toEqual([ - "applied", - "applied", - "failed", - "applied", - "applied", - "applied", - ]); - expect(results[2]).toMatchObject({ code: "LINEUP_END_IN_USE" }); - }); - - test("a patch to a deleted link after reading the page holds nothing", async () => { - const { owner } = await createHarness(); - await apply( - owner, - "owner-client", - [origin("o"), landing("l"), link("k", "o", "l"), landing("spare")].map( - (row, index) => addOp(row, index), - ), - ); - await apply(owner, "owner-client", [ - deleteOp("delete-link-k", "lineupLink:k", 1), - ]); - const results = await applyChecked(owner, "owner-client", [ - deleteOp("delete-spare", "lineupLanding:spare", 1), - patchOp("patch-deleted-k", link("k", "o", "l", { name: "Late" }), 2), - deleteOp("delete-origin-o", "lineupOrigin:o", 1), - ]); - expect(results[0]).toMatchObject({ status: "applied" }); - expect(results[2]).toMatchObject({ status: "applied" }); - }); - - test("an older client's end delete still lands ahead of its link", async () => { - const { owner, editor } = await createHarness(); - await apply( - owner, - "owner-client", - [origin("o"), landing("l"), link("k", "o", "l")].map((row, index) => - addOp(row, index), - ), - ); - // An outbox reloaded after a restart lists its rows by key (landing, - // link, origin); an older client does not reorder them. - const unflagged = await apply(editor, "old-client", [ - deleteOp("delete-landing", "lineupLanding:l", 1), - deleteOp("delete-link", "lineupLink:k", 1), - deleteOp("delete-origin", "lineupOrigin:o", 1), - ]); - expect(unflagged.map((result) => result.status)).toEqual([ - "applied", - "applied", - "applied", - ]); - }); - - test("a client that only checks link ends is not refused a delete", async () => { - const { owner, editor } = await createHarness(); - await apply( - owner, - "owner-client", - [origin("o"), landing("l"), link("k", "o", "l")].map((row, index) => - addOp(row, index), - ), - ); - // The web build that shipped checkLineupLinkEnds did not order its - // deletes yet. - const results = await apply( - editor, - "web-client", - [deleteOp("delete-origin", "lineupOrigin:o", 1)], - strategyPublicId, - { checkLineupLinkEnds: true }, - ); - expect(results[0]).toMatchObject({ status: "applied" }); - }); -}); diff --git a/convex/lineups.test.ts b/convex/lineups.test.ts new file mode 100644 index 00000000..54ca72e2 --- /dev/null +++ b/convex/lineups.test.ts @@ -0,0 +1,809 @@ +import { + convexTest, + type TestConvexForDataModel, + type TestConvexForDataModelAndIdentity, +} from "convex-test"; +import { makeFunctionReference } from "convex/server"; +import { beforeAll, describe, expect, test } from "vitest"; +import type { DataModel } from "./_generated/dataModel"; +import { markAssetReferencesReady } from "./lib/assetReferences"; +import { CURRENT_CLOUD_PROTOCOL_VERSION } from "./lib/cloudProtocol"; +import schema from "./schema"; +import { lineupPayload, type TestLineup } from "./testContent.helpers"; +import { modules } from "./test.setup"; + +const ensureCurrentUser = makeFunctionReference<"mutation">( + "users:ensureCurrentUser", +); +const createStrategy = makeFunctionReference<"mutation">( + "strategies:createWithInitialPage", +); +const createShare = makeFunctionReference<"mutation">("shares:create"); +const redeemShare = makeFunctionReference<"mutation">("shares:redeem"); +const addPage = makeFunctionReference<"mutation">("pages:add"); +const applyBatch = makeFunctionReference<"mutation">("ops:applyBatch"); +const getPageSnapshot = makeFunctionReference<"query">("page:getSnapshot"); +const getFullSnapshot = makeFunctionReference<"query">( + "strategy:getFullSnapshot", +); +const listForStrategy = makeFunctionReference<"query">( + "lineups:listForStrategy", +); +const listReferencedAssetIds = makeFunctionReference<"query">( + "images:listReferencedAssetIds", +); + +type Harness = TestConvexForDataModel; +type RootHarness = TestConvexForDataModelAndIdentity; +type Result = Record; +type LineupRow = { + publicId: string; + payload: { kind: string; data: Record }; + revision: number; + deleted: boolean; +}; + +const strategyPublicId = "lineups-strategy"; +const pagePublicId = "lineups-page"; + +function identity(subject: string) { + return { + issuer: "https://lineups.test", + subject, + tokenIdentifier: `lineups|${subject}`, + name: subject, + }; +} + +async function createHarness(): Promise<{ + t: RootHarness; + owner: Harness; + editor: Harness; + viewer: Harness; +}> { + const t = convexTest(schema, modules); + const owner = t.withIdentity(identity("owner")); + const editor = t.withIdentity(identity("editor")); + const viewer = t.withIdentity(identity("viewer")); + for (const user of [owner, editor, viewer]) { + await user.mutation(ensureCurrentUser, { + clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, + }); + } + await owner.mutation(createStrategy, { + clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, + publicId: strategyPublicId, + name: "Lineups", + mapData: "ascent", + initialPagePublicId: pagePublicId, + initialPageName: "Page 1", + initialPageIsAttack: true, + }); + for (const [user, role] of [ + [editor, "editor"], + [viewer, "viewer"], + ] as const) { + await owner.mutation(createShare, { + clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, + targetType: "strategy", + targetPublicId: strategyPublicId, + token: `${role}-token`, + role, + }); + await user.mutation(redeemShare, { + clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, + token: `${role}-token`, + }); + } + return { t, owner, editor, viewer }; +} + +/** A lineup row as a test sends it: its key and its payload. */ +function lineup(id: string, details: TestLineup = {}) { + return { key: id, payload: lineupPayload(id, details) }; +} + +type Row = ReturnType; + +function addOp(row: Row, sortIndex: number, page = pagePublicId) { + return { + opId: `add-${row.key}`, + type: "lineup.add", + lineupPublicId: row.key, + pagePublicId: page, + payload: row.payload, + sortIndex, + }; +} + +function patchOp(opId: string, row: Row, expectedLineupRevision: number) { + return { + opId, + type: "lineup.patch", + lineupPublicId: row.key, + pagePublicId, + payload: row.payload, + expectedLineupRevision, + }; +} + +function deleteOp(opId: string, key: string, expectedLineupRevision: number) { + return { + opId, + type: "lineup.delete", + lineupPublicId: key, + pagePublicId, + expectedLineupRevision, + }; +} + +async function apply( + user: Harness, + clientId: string, + ops: Array>, + strategy = strategyPublicId, +): Promise { + const response = (await user.mutation(applyBatch, { + strategyPublicId: strategy, + clientId, + clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, + ops, + })) as { results: Result[] }; + return response.results; +} + +async function pageLineups( + user: Harness, + strategy = strategyPublicId, + page = pagePublicId, +): Promise { + const snapshot = (await user.query(getPageSnapshot, { + clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, + strategyPublicId: strategy, + pagePublicId: page, + })) as { lineups: LineupRow[] }; + return snapshot.lineups; +} + +function byKey(rows: LineupRow[]): Map { + return new Map(rows.map((row) => [row.publicId, row])); +} + +function liveKeys(rows: LineupRow[]): string[] { + return rows.filter((row) => !row.deleted).map((row) => row.publicId); +} + +async function addSecondPage(owner: Harness, publicId: string) { + await owner.mutation(addPage, { + clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, + strategyPublicId, + expectedRevision: 0, + pagePublicId: publicId, + name: "Page 2", + sortIndex: 1, + isAttack: false, + }); +} + +async function agentSummary(t: RootHarness): Promise { + return await t.run(async (ctx) => { + const strategy = await ctx.db + .query("strategies") + .withIndex("by_publicId", (q) => q.eq("publicId", strategyPublicId)) + .unique(); + const summary = await ctx.db + .query("strategyAgentSummaries") + .withIndex("by_strategyId", (q) => q.eq("strategyId", strategy!._id)) + .unique(); + return summary?.agentTypes ?? []; + }); +} + +beforeAll(() => { + // Page snapshots serialize asset URLs from the R2 public base. + process.env.R2_ACCOUNT_ID = "lineups-account"; + process.env.R2_BUCKET = "lineups-bucket"; + process.env.R2_ACCESS_KEY_ID = "lineups-access-key"; + process.env.R2_SECRET_ACCESS_KEY = "lineups-secret"; + process.env.R2_PUBLIC_BASE_URL = "https://assets.lineups.test"; + process.env.R2_S3_ENDPOINT = "https://lineups.r2.test"; +}); + +/** Two lineups from different origins aiming at one shared landing. */ +const fanIn = [ + lineup("from-heaven", { + originId: "heaven", + landingId: "site", + name: "From heaven", + }), + lineup("from-mid", { originId: "mid", landingId: "site", name: "From mid" }), +]; + +/** Two lineups thrown from one shared origin to different landings. */ +const fanOut = [ + lineup("to-default", { + originId: "spawn", + landingId: "default", + name: "Default plant", + }), + lineup("to-hell", { originId: "spawn", landingId: "hell", name: "Hell" }), +]; + +describe("one row per lineup", () => { + test("a lineup round-trips with its origin and landing intact", async () => { + const { owner } = await createHarness(); + const row = lineup("smoke", { + originId: "smoke-origin", + landingId: "smoke-landing", + agentType: "brimstone", + originPosition: { dx: 120, dy: 340 }, + landingPosition: { dx: 400, dy: 220 }, + name: "A main smoke", + youtubeLink: "https://youtu.be/smoke", + notes: "Jump throw", + }); + const results = await apply(owner, "owner-client", [addOp(row, 0)]); + expect(results[0]).toMatchObject({ status: "applied", appliedRevision: 1 }); + + const rows = await pageLineups(owner); + expect(rows).toHaveLength(1); + expect(rows[0]).toMatchObject({ + publicId: "smoke", + revision: 1, + deleted: false, + }); + expect(rows[0]!.payload).toEqual(row.payload); + + const full = (await owner.query(getFullSnapshot, { + clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, + strategyPublicId, + })) as { lineups: LineupRow[] }; + expect(full.lineups.map((entry) => entry.payload)).toEqual([row.payload]); + + const listed = (await owner.query(listForStrategy, { + clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, + strategyPublicId, + })) as LineupRow[]; + expect(listed.map((entry) => entry.payload)).toEqual([row.payload]); + }); + + test("lineups sharing a landing each store and read back their own copy", async () => { + const { owner } = await createHarness(); + const results = await apply( + owner, + "owner-client", + fanIn.map((row, index) => addOp(row, index)), + ); + expect(results.map((result) => result.status)).toEqual([ + "applied", + "applied", + ]); + + const rows = byKey(await pageLineups(owner)); + expect([...rows.keys()]).toEqual(["from-heaven", "from-mid"]); + for (const row of fanIn) { + expect(rows.get(row.key)!.payload).toEqual(row.payload); + } + expect(rows.get("from-heaven")!.payload.data.landing).toEqual( + rows.get("from-mid")!.payload.data.landing, + ); + }); + + test("lineups sharing an origin each store and read back their own copy", async () => { + const { owner } = await createHarness(); + const results = await apply( + owner, + "owner-client", + fanOut.map((row, index) => addOp(row, index)), + ); + expect(results.map((result) => result.status)).toEqual([ + "applied", + "applied", + ]); + + const rows = byKey(await pageLineups(owner)); + for (const row of fanOut) { + expect(rows.get(row.key)!.payload).toEqual(row.payload); + } + expect(rows.get("to-default")!.payload.data.origin).toEqual( + rows.get("to-hell")!.payload.data.origin, + ); + }); + + test("a lineup may share its id with its landing", async () => { + const { owner } = await createHarness(); + const row = lineup("same-id", { landingId: "same-id" }); + const results = await apply(owner, "owner-client", [addOp(row, 0)]); + expect(results[0]).toMatchObject({ status: "applied" }); + expect((await pageLineups(owner))[0]!.payload).toEqual(row.payload); + }); + + test("moving a shared spot patches each lineup that carries it on its own", async () => { + const { owner } = await createHarness(); + await apply( + owner, + "owner-client", + fanIn.map((row, index) => addOp(row, index)), + ); + const moved = { dx: 10, dy: 20 }; + + const first = await apply(owner, "owner-client", [ + patchOp( + "move-site-heaven", + lineup("from-heaven", { + originId: "heaven", + landingId: "site", + landingPosition: moved, + name: "From heaven", + }), + 1, + ), + ]); + expect(first[0]).toMatchObject({ status: "applied", appliedRevision: 2 }); + // The other row keeps its own copy until its own patch lands. + let rows = byKey(await pageLineups(owner)); + expect(rows.get("from-heaven")).toMatchObject({ revision: 2 }); + expect(rows.get("from-mid")).toMatchObject({ revision: 1 }); + expect(rows.get("from-mid")!.payload).toEqual(fanIn[1]!.payload); + + const second = await apply(owner, "owner-client", [ + patchOp( + "move-site-mid", + lineup("from-mid", { + originId: "mid", + landingId: "site", + landingPosition: moved, + name: "From mid", + }), + 1, + ), + ]); + expect(second[0]).toMatchObject({ status: "applied", appliedRevision: 2 }); + rows = byKey(await pageLineups(owner)); + for (const key of ["from-heaven", "from-mid"]) { + expect(rows.get(key)).toMatchObject({ revision: 2 }); + expect(rows.get(key)!.payload.data.landing).toMatchObject({ + id: "site", + ability: { position: moved, lineUpID: "site" }, + }); + } + }); + + test("teammates editing different lineups that share a spot never conflict", async () => { + const { owner, editor } = await createHarness(); + await apply( + owner, + "owner-client", + fanIn.map((row, index) => addOp(row, index)), + ); + + // Both edit from the same loaded revision (1) of their own lineup: the + // owner moves the shared landing on theirs, the editor writes notes on + // the other. + const ownerResults = await apply(owner, "owner-client", [ + patchOp( + "owner-moves-site", + lineup("from-heaven", { + originId: "heaven", + landingId: "site", + landingPosition: { dx: 5, dy: 5 }, + name: "From heaven", + }), + 1, + ), + ]); + const editorResults = await apply(editor, "editor-client", [ + patchOp( + "editor-notes-mid", + lineup("from-mid", { + originId: "mid", + landingId: "site", + name: "From mid", + notes: "Editor notes", + }), + 1, + ), + ]); + expect(ownerResults[0]).toMatchObject({ + status: "applied", + appliedRevision: 2, + }); + expect(editorResults[0]).toMatchObject({ + status: "applied", + appliedRevision: 2, + }); + + const rows = byKey(await pageLineups(owner)); + expect(rows.get("from-heaven")!.payload.data).toMatchObject({ + notes: "", + landing: { ability: { position: { dx: 5, dy: 5 } } }, + }); + expect(rows.get("from-mid")!.payload.data).toMatchObject({ + notes: "Editor notes", + landing: { ability: { position: { dx: 0, dy: 0 } } }, + }); + }); + + test("a stale edit to the same lineup is a conflict, not a silent overwrite", async () => { + const { owner, editor } = await createHarness(); + await apply(owner, "owner-client", [addOp(fanIn[0]!, 0)]); + const ownerEdit = lineup("from-heaven", { + originId: "heaven", + landingId: "site", + name: "Owner name", + }); + await apply(owner, "owner-client", [patchOp("owner-renames", ownerEdit, 1)]); + + const stale = await apply(editor, "editor-client", [ + patchOp( + "editor-renames", + lineup("from-heaven", { + originId: "heaven", + landingId: "site", + name: "Stale name", + }), + 1, + ), + ]); + expect(stale[0]).toMatchObject({ + status: "rejected", + reason: "revision_mismatch", + current: { type: "lineup", revision: 2, value: ownerEdit.payload }, + }); + const row = byKey(await pageLineups(owner)).get("from-heaven"); + expect(row).toMatchObject({ revision: 2 }); + expect(row!.payload).toEqual(ownerEdit.payload); + }); + + test("deleting a spot's lineups leaves a teammate's new lineup from that spot", async () => { + const { owner, editor } = await createHarness(); + await apply( + owner, + "owner-client", + fanIn.map((row, index) => addOp(row, index)), + ); + + // The owner deletes every lineup into "site" while the editor, who has + // not seen that yet, places a new lineup into the same spot. + const deletes = await apply(owner, "owner-client", [ + deleteOp("delete-heaven", "from-heaven", 1), + deleteOp("delete-mid", "from-mid", 1), + ]); + const added = await apply(editor, "editor-client", [ + addOp(lineup("from-garage", { originId: "garage", landingId: "site" }), 2), + ]); + expect(deletes.map((result) => result.status)).toEqual([ + "applied", + "applied", + ]); + expect(added[0]).toMatchObject({ status: "applied" }); + + const rows = await pageLineups(owner); + expect(liveKeys(rows)).toEqual(["from-garage"]); + expect(byKey(rows).get("from-garage")!.payload.data.landing).toMatchObject( + { id: "site" }, + ); + }); + + test("a viewer reads lineups but cannot write them", async () => { + const { owner, viewer } = await createHarness(); + await apply(owner, "owner-client", [addOp(fanIn[0]!, 0)]); + await expect( + apply(viewer, "viewer-client", [addOp(fanIn[1]!, 1)]), + ).rejects.toThrow("Forbidden"); + await expect( + apply(viewer, "viewer-client", [ + patchOp( + "viewer-renames", + lineup("from-heaven", { + originId: "heaven", + landingId: "site", + name: "Viewer", + }), + 1, + ), + ]), + ).rejects.toThrow("Forbidden"); + expect((await pageLineups(viewer)).map((row) => row.publicId)).toEqual([ + "from-heaven", + ]); + }); +}); + +describe("a lineup row must be drawable on its own", () => { + test("a row missing its origin or landing, or keyed apart from its id, is refused", async () => { + const { owner } = await createHarness(); + const valid = lineupPayload("k"); + const withData = (id: string, data: Record) => ({ + ...addOp(lineup(id), 0), + payload: { ...valid, data: { ...valid.data, id, ...data } }, + }); + const withoutOrigin = withData("no-origin", { origin: undefined }); + delete (withoutOrigin.payload.data as Record).origin; + const withoutLanding = withData("no-landing", {}); + delete (withoutLanding.payload.data as Record).landing; + const originWithoutAgent = withData("no-agent", { + origin: { id: "o" }, + }); + const landingWithoutAbility = withData("no-ability", { + landing: { id: "l" }, + }); + const originWithoutId = withData("no-origin-id", { + origin: { agent: valid.data.origin.agent }, + }); + const mismatchedKey = { ...addOp(lineup("k"), 0), lineupPublicId: "other" }; + const graphKey = { ...addOp(lineup("k"), 0), lineupPublicId: "lineupLink:k" }; + + const results = await apply(owner, "owner-client", [ + withoutOrigin, + withoutLanding, + originWithoutAgent, + landingWithoutAbility, + originWithoutId, + mismatchedKey, + graphKey, + ]); + expect(results).toHaveLength(7); + for (const result of results) { + expect(result).toMatchObject({ + status: "failed", + code: "INVALID_LINEUP_PAYLOAD_DATA", + }); + } + expect(await pageLineups(owner)).toEqual([]); + }); + + test("a patch that drops an end is refused and leaves the row as it was", async () => { + const { owner } = await createHarness(); + await apply(owner, "owner-client", [addOp(fanIn[0]!, 0)]); + const { landing: _landing, ...withoutLanding } = fanIn[0]!.payload.data; + const results = await apply(owner, "owner-client", [ + { + ...patchOp("drop-landing", fanIn[0]!, 1), + payload: { ...fanIn[0]!.payload, data: withoutLanding }, + }, + ]); + expect(results[0]).toMatchObject({ + status: "failed", + code: "INVALID_LINEUP_PAYLOAD_DATA", + }); + const row = (await pageLineups(owner))[0]!; + expect(row).toMatchObject({ revision: 1 }); + expect(row.payload).toEqual(fanIn[0]!.payload); + }); + + test("a row of an old graph kind is refused by the contract", async () => { + const { owner } = await createHarness(); + for (const kind of ["lineupLink", "lineupOrigin", "lineupGroup"]) { + await expect( + apply(owner, "old-client", [ + { + opId: `add-${kind}`, + type: "lineup.add", + lineupPublicId: `${kind}:k`, + pagePublicId, + payload: { + kind, + payloadVersion: 1, + data: { id: "k", originId: "o", landingId: "l", images: [] }, + }, + sortIndex: 0, + }, + ]), + ).rejects.toThrow(/Validator error|ArgumentValidationError/); + } + expect(await pageLineups(owner)).toEqual([]); + }); +}); + +describe("lineup row keys belong to their strategy and page", () => { + test("the same lineup ids in two strategies are separate rows", async () => { + const { owner } = await createHarness(); + const copyId = "local-duplicate"; + const copyPage = "local-duplicate-page"; + await owner.mutation(createStrategy, { + clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, + publicId: copyId, + name: "Copy", + mapData: "ascent", + initialPagePublicId: copyPage, + initialPageName: "Page 1", + initialPageIsAttack: true, + }); + // A strategy duplicated on the device keeps every lineup id; each upload + // keeps them too (ids only need to be unique within a strategy). + for (const [strategy, page] of [ + [strategyPublicId, pagePublicId], + [copyId, copyPage], + ] as const) { + const results = await apply( + owner, + `upload-${strategy}`, + fanIn.map((row, index) => addOp(row, index, page)), + strategy, + ); + expect(results.map((result) => result.status)).toEqual([ + "applied", + "applied", + ]); + } + // Editing one copy leaves the other untouched. + await apply( + owner, + "edit-copy", + [ + { + ...patchOp( + "rename-in-copy", + lineup("from-heaven", { + originId: "heaven", + landingId: "site", + name: "Copy name", + }), + 1, + ), + pagePublicId: copyPage, + }, + ], + copyId, + ); + const original = byKey(await pageLineups(owner)); + const copy = byKey(await pageLineups(owner, copyId, copyPage)); + expect(original.get("from-heaven")!.payload.data.name).toBe("From heaven"); + expect(copy.get("from-heaven")!.payload.data.name).toBe("Copy name"); + }); + + test("a lineup row is never moved to another page by a patch", async () => { + const { owner } = await createHarness(); + const secondPage = "lineups-page-2"; + await addSecondPage(owner, secondPage); + await apply(owner, "page-1-client", [addOp(lineup("k"), 0)]); + + // Another page wants the same key: the add is refused, and "Keep mine" + // would turn it into a patch naming the other page. + const clashing = lineup("k", { originId: "o2", name: "Page 2 lineup" }); + const added = await apply(owner, "page-2-client", [ + addOp(clashing, 0, secondPage), + ]); + expect(added[0]).toMatchObject({ + status: "rejected", + reason: "already_exists", + }); + const moved = await apply(owner, "page-2-client", [ + { ...patchOp("keep-mine", clashing, 1), pagePublicId: secondPage }, + ]); + expect(moved[0]).toMatchObject({ + status: "failed", + code: "LINEUP_PAGE_MISMATCH", + }); + + // The row stays on its page, untouched. + const first = await pageLineups(owner); + expect(first.map((row) => row.publicId)).toEqual(["k"]); + expect(first[0]!.payload).toEqual(lineup("k").payload); + expect(await pageLineups(owner, strategyPublicId, secondPage)).toEqual([]); + }); + + test("the same lineup added twice with a different order is one add", async () => { + const { owner, editor } = await createHarness(); + const first = await apply(owner, "owner-client", [addOp(fanIn[0]!, 3)]); + const second = await apply(editor, "editor-client", [addOp(fanIn[0]!, 7)]); + expect(first[0]).toMatchObject({ status: "applied" }); + expect(second[0]).toMatchObject({ status: "noop" }); + const conflicting = await apply(editor, "editor-client", [ + { + ...addOp( + lineup("from-heaven", { + originId: "heaven", + landingId: "site", + agentType: "jett", + }), + 7, + ), + opId: "different-content", + }, + ]); + expect(conflicting[0]).toMatchObject({ + status: "rejected", + reason: "already_exists", + }); + }); +}); + +describe("lineup images", () => { + test("a lineup's images are referenced while it lives and released when it is deleted", async () => { + const { t, owner } = await createHarness(); + await t.run(markAssetReferencesReady); + await t.run(async (ctx) => { + const strategy = await ctx.db + .query("strategies") + .withIndex("by_publicId", (q) => q.eq("publicId", strategyPublicId)) + .unique(); + const now = Date.now(); + for (const publicId of ["shot-1", "shot-2"]) { + await ctx.db.insert("imageAssets", { + publicId, + provider: "r2", + strategyId: strategy!._id, + objectKey: `tests/${publicId}.png`, + uploadStatus: "active", + fileExtension: ".png", + createdAt: now, + updatedAt: now, + }); + } + }); + const row = lineup("k", { + images: [ + { id: "shot-1", fileExtension: ".png" }, + { id: "shot-2", fileExtension: ".png" }, + ], + }); + await apply(owner, "owner-client", [addOp(row, 0)]); + + const snapshot = (await owner.query(getPageSnapshot, { + clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, + strategyPublicId, + pagePublicId, + })) as { assets: Array<{ publicId: string }> }; + expect(snapshot.assets.map((asset) => asset.publicId).sort()).toEqual([ + "shot-1", + "shot-2", + ]); + const references = await t.run(async (ctx) => + (await ctx.db.query("assetReferences").collect()).filter( + (reference) => reference.lineupId !== undefined, + ), + ); + expect( + references.map((reference) => reference.assetPublicId).sort(), + ).toEqual(["shot-1", "shot-2"]); + const asked = { + strategyPublicId, + assetPublicIds: ["shot-1", "shot-2", "never-shown"], + }; + expect(await owner.query(listReferencedAssetIds, asked)).toEqual([ + "shot-1", + "shot-2", + ]); + + // Dropping one image from the lineup releases only that one. + await apply(owner, "owner-client", [ + patchOp( + "drop-shot-2", + lineup("k", { images: [{ id: "shot-1", fileExtension: ".png" }] }), + 1, + ), + ]); + expect(await owner.query(listReferencedAssetIds, asked)).toEqual([ + "shot-1", + ]); + + await apply(owner, "owner-client", [deleteOp("delete-k", "k", 2)]); + expect(await owner.query(listReferencedAssetIds, asked)).toEqual([]); + }); +}); + +describe("agent summary", () => { + test("an origin shared by several lineups counts once, distinct origins count each", async () => { + const { t, owner } = await createHarness(); + await apply(owner, "owner-client", [ + // One astra origin shared by two lineups. + addOp(lineup("astra-a", { originId: "astra", agentType: "astra" }), 0), + addOp(lineup("astra-b", { originId: "astra", agentType: "astra" }), 1), + // Two viper origins. + addOp(lineup("viper-a", { originId: "viper-1", agentType: "viper" }), 2), + addOp(lineup("viper-b", { originId: "viper-2", agentType: "viper" }), 3), + ]); + // Counted per lineup, astra would tie viper and sort first by name; + // counted per origin, viper (2) leads astra (1). + expect(await agentSummary(t)).toEqual(["viper", "astra"]); + + // Deleting every lineup from an origin drops its agent. + await apply(owner, "owner-client", [ + deleteOp("delete-astra-a", "astra-a", 1), + deleteOp("delete-astra-b", "astra-b", 1), + ]); + expect(await agentSummary(t)).toEqual(["viper"]); + }); +}); diff --git a/convex/mediaLimits.test.ts b/convex/mediaLimits.test.ts index 83a67346..d1d26771 100644 --- a/convex/mediaLimits.test.ts +++ b/convex/mediaLimits.test.ts @@ -16,7 +16,11 @@ import * as maintenance from "./maintenance"; import schema from "./schema"; import * as strategies from "./strategies"; import { modules } from "./test.setup"; -import { insertElement, insertLineup } from "./testContent.helpers"; +import { + insertElement, + insertLineup, + lineupPayload, +} from "./testContent.helpers"; const ensureCurrentUser = makeFunctionReference<"mutation">( "users:ensureCurrentUser", @@ -360,18 +364,9 @@ describe("media cleanup stays within transaction limits", () => { { opId: "link-shows-old-image", type: "lineup.add", - lineupPublicId: "lineupLink:link", + lineupPublicId: "link", pagePublicId, - payload: { - kind: "lineupLink", - payloadVersion: 1, - data: { - id: "link", - originId: "o", - landingId: "l", - images: [{ id: "old-image" }], - }, - }, + payload: lineupPayload("link", { images: [{ id: "old-image" }] }), sortIndex: 0, }, ], @@ -417,13 +412,9 @@ describe("asset references follow their content", () => { { opId: "add-link", type: "lineup.add", - lineupPublicId: "lineupLink:k", + lineupPublicId: "k", pagePublicId, - payload: { - kind: "lineupLink", - payloadVersion: 1, - data: { id: "k", originId: "o", landingId: "l", images: [{ id: "a" }, { id: "b" }] }, - }, + payload: lineupPayload("k", { images: [{ id: "a" }, { id: "b" }] }), sortIndex: 0, }, ], @@ -451,13 +442,9 @@ describe("asset references follow their content", () => { { opId: "drop-b", type: "lineup.patch", - lineupPublicId: "lineupLink:k", + lineupPublicId: "k", pagePublicId, - payload: { - kind: "lineupLink", - payloadVersion: 1, - data: { id: "k", originId: "o", landingId: "l", images: [{ id: "a" }] }, - }, + payload: lineupPayload("k", { images: [{ id: "a" }] }), expectedLineupRevision: 1, }, ], @@ -520,21 +507,14 @@ describe("asset references follow their content", () => { }); for (let index = 0; index < 3; index++) { await ctx.db.insert("lineups", { - publicId: `lineupLink:before-${index}`, + publicId: `before-${index}`, strategyId, pageId, - payloadKind: "lineupLink", + payloadKind: "lineup", payloadVersion: 1, - payload: { - kind: "lineupLink", - payloadVersion: 1, - data: { - id: `before-${index}`, - originId: "o", - landingId: "l", - images: [{ id: `link-image-${index}` }], - }, - }, + payload: lineupPayload(`before-${index}`, { + images: [{ id: `link-image-${index}` }], + }), sortIndex: index, revision: 1, deleted: index === 2, @@ -559,10 +539,10 @@ describe("asset references follow their content", () => { ).mismatched, ]; expect((await mismatched()).sort()).toEqual([ + "before-0", + "before-1", + "before-2", "before-image", - "lineupLink:before-0", - "lineupLink:before-1", - "lineupLink:before-2", ]); await t.mutation(backfillAssetReferences, { @@ -632,21 +612,12 @@ describe("before the reference backfill", () => { }); } await ctx.db.insert("lineups", { - publicId: "lineupLink:live", + publicId: "live", strategyId, pageId, - payloadKind: "lineupLink", + payloadKind: "lineup", payloadVersion: 1, - payload: { - kind: "lineupLink", - payloadVersion: 1, - data: { - id: "live", - originId: "o", - landingId: "l", - images: [{ id: "lineup-shown" }], - }, - }, + payload: lineupPayload("live", { images: [{ id: "lineup-shown" }] }), sortIndex: 0, revision: 1, deleted: false, @@ -710,8 +681,8 @@ describe("before the reference backfill", () => { }); describe("the backfill and its gate", () => { - /// A lineup link showing [images] images, inserted without reference - /// rows, as the previous version wrote it. + /// A lineup showing [images] images, inserted without reference rows, as + /// the previous version wrote it. async function insertOldLineup( t: RootHarness, linkId: string, @@ -721,23 +692,16 @@ describe("the backfill and its gate", () => { await t.run(async (ctx) => { const now = Date.now(); await ctx.db.insert("lineups", { - publicId: `lineupLink:${linkId}`, + publicId: linkId, strategyId, pageId, - payloadKind: "lineupLink", + payloadKind: "lineup", payloadVersion: 1, - payload: { - kind: "lineupLink", - payloadVersion: 1, - data: { - id: linkId, - originId: "o", - landingId: "l", - images: Array.from({ length: images }, (_, index) => ({ - id: `${linkId}-${index}`, - })), - }, - }, + payload: lineupPayload(linkId, { + images: Array.from({ length: images }, (_, index) => ({ + id: `${linkId}-${index}`, + })), + }), sortIndex: 0, revision: 1, deleted: false, @@ -967,12 +931,12 @@ describe("the backfill and its gate", () => { }); expect(result.unavailable).toEqual([ { - content: "lineupLink:shown", + content: "shown", assetPublicId: "shown-1", statuses: ["deleted"], }, { - content: "lineupLink:shown", + content: "shown", assetPublicId: "shown-2", statuses: ["pending"], }, @@ -989,23 +953,16 @@ describe("purges stay within transaction limits", () => { await t.run(async (ctx) => { for (let lineup = 0; lineup < 5; lineup++) { await insertLineup(ctx, { - publicId: `lineupLink:many-${lineup}`, + publicId: `many-${lineup}`, strategyId, pageId, - payloadKind: "lineupLink", + payloadKind: "lineup", payloadVersion: 1, - payload: { - kind: "lineupLink", - payloadVersion: 1, - data: { - id: `many-${lineup}`, - originId: "o", - landingId: "l", - images: Array.from({ length: 850 }, (_, image) => ({ - id: `image-${lineup}-${image}`, - })), - }, - }, + payload: lineupPayload(`many-${lineup}`, { + images: Array.from({ length: 850 }, (_, image) => ({ + id: `image-${lineup}-${image}`, + })), + }), sortIndex: lineup, revision: 2, deleted: true, @@ -1236,23 +1193,16 @@ describe("duplicate stays within transaction limits", () => { await t.run(async (ctx) => { const now = Date.now(); await insertLineup(ctx, { - publicId: "lineupLink:gallery", + publicId: "gallery", strategyId, pageId, - payloadKind: "lineupLink", + payloadKind: "lineup", payloadVersion: 1, - payload: { - kind: "lineupLink", - payloadVersion: 1, - data: { - id: "gallery", - originId: "o", - landingId: "l", - images: Array.from({ length: count }, (_, index) => ({ - id: `gallery-${index}`, - })), - }, - }, + payload: lineupPayload("gallery", { + images: Array.from({ length: count }, (_, index) => ({ + id: `gallery-${index}`, + })), + }), sortIndex: 0, revision: 1, deleted: false, diff --git a/convex/ops.ts b/convex/ops.ts index 1be82a06..33152592 100644 --- a/convex/ops.ts +++ b/convex/ops.ts @@ -335,20 +335,13 @@ function assertElementPayload(payload: unknown): ElementPayload { return payload as ElementPayload; } -const lineupGraphKinds = new Set([ - "lineupOrigin", - "lineupLanding", - "lineupLink", -]); - function invalidLineupData(message: string) { return errorWithCode("INVALID_LINEUP_PAYLOAD_DATA", message); } -/// Checks one lineup row before it is stored. A graph row (origin, landing -/// or link) is keyed `:`: entity ids repeat across -/// kinds (a landing made with a link shares the link's id), so the kind is -/// part of the key, and the key must name the entity the payload holds. +/// Checks one lineup row before it is stored. A row is one whole lineup, +/// keyed by its id, holding its origin and landing: every row can be drawn +/// on its own, whatever other rows exist. function assertLineupPayload( payload: unknown, lineupPublicId: string, @@ -356,10 +349,9 @@ function assertLineupPayload( if (!isRecord(payload)) { throw errorWithCode("MISSING_LINEUP_PAYLOAD", "Missing lineup payload"); } - // Lineups are stored only as graph rows. The legacy `lineupGroup` shape is - // not part of the contract (argument validation already refuses it); this - // check keeps the rule where the row is written. - if (!lineupGraphKinds.has(payload.kind)) { + // Argument validation already refuses any other kind; this check keeps + // the rule where the row is written. + if (payload.kind !== "lineup") { throw errorWithCode( "INVALID_LINEUP_PAYLOAD_KIND", "Invalid lineup payload kind", @@ -381,26 +373,28 @@ function assertLineupPayload( if (typeof data.id !== "string" || data.id.length === 0) { throw invalidLineupData("Lineup payload has no id"); } - if (lineupPublicId !== `${payload.kind}:${data.id}`) { + if (lineupPublicId !== data.id) { throw invalidLineupData("Lineup key does not match its payload"); } - // Each entity must carry what hydration draws; a row without it would load - // as nothing and read as a deletion nobody made. - if (payload.kind === "lineupOrigin" && !isRecord(data.agent)) { - throw invalidLineupData("Lineup origin has no agent"); - } - if (payload.kind === "lineupLanding" && !isRecord(data.ability)) { - throw invalidLineupData("Lineup landing has no ability"); - } - if ( - payload.kind === "lineupLink" && - (typeof data.originId !== "string" || typeof data.landingId !== "string") - ) { - throw invalidLineupData("Lineup link does not name its origin and landing"); + // The row must carry both ends hydration draws; a row without them would + // load as nothing and read as a deletion nobody made. + if (!hasEnd(data.origin, "agent") || !hasEnd(data.landing, "ability")) { + throw invalidLineupData("Lineup has no origin or landing"); } return payload as LineupPayload; } +/// Whether [end] is an origin or landing: an id and the marker it places +/// under [markerKey]. +function hasEnd(end: unknown, markerKey: "agent" | "ability"): boolean { + return ( + isRecord(end) && + typeof end.id === "string" && + end.id.length > 0 && + isRecord(end[markerKey]) + ); +} + function setIfChanged( patch: Record, key: string, @@ -510,8 +504,7 @@ async function getElementByPublicIdOrNull( } /// A lineup row key is unique within its strategy only: a strategy copied -/// before the graph synced natively shares its original's item ids, so both -/// convert to the same `lineupLanding:` and `lineupLink:` keys. +/// on a device keeps its original's lineup ids, so both upload the same keys. async function getLineupByPublicIdOrNull( ctx: MutationCtx, strategyId: Id<"strategies">, @@ -525,122 +518,6 @@ async function getLineupByPublicIdOrNull( .unique(); } -/// A link is drawn only while its origin and landing are live rows on its -/// page (see lineUpGraphFromCloudRows on the client). Storing a link whose -/// end is gone, say one a teammate deleted while it was being placed, would -/// make the lineup vanish on the next load with nothing on screen, so it is -/// refused instead. Ends are read in this transaction: one added earlier in -/// the same batch counts. -async function assertLinkEndsLive( - ctx: MutationCtx, - strategyId: Id<"strategies">, - pageId: Id<"pages">, - payload: LineupPayload, -): Promise { - if (payload.kind !== "lineupLink") return; - const data = payload.data as { originId: string; landingId: string }; - for (const endKey of [ - `lineupOrigin:${data.originId}`, - `lineupLanding:${data.landingId}`, - ]) { - const end = await getLineupByPublicIdOrNull(ctx, strategyId, endKey); - if (end === null || end.deleted || end.pageId !== pageId) { - // The client matches this text (lineupLinkEndMissingMessage) to - // re-send the link as it was on "Keep mine". - throw errorWithCode( - "LINEUP_LINK_END_MISSING", - "This lineup's origin or landing spot is no longer on the page", - ); - } - } -} - -/// The origin and landing each live link names, per page, as this batch -/// has left them. A page's links are read once, when an end on it is first -/// deleted, and the batch's own link writes update that copy, so a batch -/// deleting many lineups reads each page's links once rather than once per -/// end. -class LiveLinkEnds { - private readonly byPage = new Map< - Id<"pages">, - Map - >(); - - async of(ctx: MutationCtx, pageId: Id<"pages">) { - let links = this.byPage.get(pageId); - if (links === undefined) { - const rows = await ctx.db - .query("lineups") - .withIndex("by_pageId_and_payloadKind_and_deleted", (q) => - q - .eq("pageId", pageId) - .eq("payloadKind", "lineupLink") - .eq("deleted", false), - ) - .collect(); - links = new Map(rows.map((row) => [row.publicId, linkEnds(row.payload)])); - this.byPage.set(pageId, links); - } - return links; - } - - /// Follows an accepted lineup op. A page not read yet needs nothing: its - /// read will see the write. - recordAccepted(op: StrategyOp, pageId: Id<"pages"> | undefined): void { - const links = pageId === undefined ? undefined : this.byPage.get(pageId); - const publicId = op.entityPublicId; - if (links === undefined || publicId === undefined) return; - if (!publicId.startsWith("lineupLink:")) return; - if (op.kind === "delete") { - links.delete(publicId); - } else if (op.kind === "add") { - // An accepted add inserts the link or restores its tombstone. - links.set(publicId, linkEnds(op.payload as LineupPayload)); - } else if (op.payload !== undefined && links.has(publicId)) { - // A patch never changes whether a link is live; one to a tombstone - // stays out. - links.set(publicId, linkEnds(op.payload as LineupPayload)); - } - } -} - -function linkEnds(payload: LineupPayload) { - const data = payload.data as Record; - return { originId: data.originId, landingId: data.landingId }; -} - -/// The mirror of assertLinkEndsLive: deleting an origin or landing that a -/// live link on its page still names would leave that link undrawable, so -/// its lineup would vanish on the next load while every chip says synced. -/// That happens when a teammate's new link lands first and this client -/// never saw it. The delete is refused instead. A link deleted earlier in -/// the same batch is already gone here, so a client deleting a whole -/// lineup sends its link first. -async function assertLineupEndUnused( - ctx: MutationCtx, - end: Doc<"lineups">, - liveLinkEnds: LiveLinkEnds, -): Promise { - const endField = - end.payloadKind === "lineupOrigin" - ? "originId" - : end.payloadKind === "lineupLanding" - ? "landingId" - : null; - if (endField === null) return; - const endId = (end.payload.data as { id: string }).id; - const links = await liveLinkEnds.of(ctx, end.pageId); - for (const ends of links.values()) { - if (ends[endField] === endId) { - // The client matches this text (lineupEndInUseMessage). - throw errorWithCode( - "LINEUP_END_IN_USE", - "Another lineup still uses this origin or landing spot", - ); - } - } -} - async function getPageContent( ctx: MutationCtx, pageId: Id<"pages">, @@ -1367,10 +1244,7 @@ async function applyLineupOp( ctx: MutationCtx, strategy: Doc<"strategies">, op: StrategyOp, - checkLinkEnds: boolean, - checkEndDeletes: boolean, checkTrashedPageDeletes: boolean, - liveLinkEnds: LiveLinkEnds, ): Promise { const publicId = op.entityPublicId; if (publicId === undefined) { @@ -1399,9 +1273,6 @@ async function applyLineupOp( existing.pageId, ); } - if (checkLinkEnds) { - await assertLinkEndsLive(ctx, strategy._id, page._id, payload); - } const revision = existing.revision + 1; await ctx.db.patch(existing._id, { pageId: page._id, @@ -1419,9 +1290,9 @@ async function applyLineupOp( eventPageId: page._id, }; } - // The row already holds exactly this entity: two clients converted the - // same legacy group, or a retry landed twice. Order is not part of a - // lineup's content (each client places new rows after the highest it + // The row already holds exactly this lineup: a retry landed twice, or + // two devices uploaded the same copied strategy. Order is not part of + // a lineup's content (each client places new rows after the highest it // knows), so a different sortIndex alone is still the same add. const identical = existing.pageId === page._id && valuesEqual(existing.payload, payload); @@ -1432,9 +1303,6 @@ async function applyLineupOp( existing.pageId, ); } - if (checkLinkEnds) { - await assertLinkEndsLive(ctx, strategy._id, page._id, payload); - } const now = Date.now(); await ctx.db.insert("lineups", { publicId, @@ -1470,9 +1338,6 @@ async function applyLineupOp( existing.pageId, ); } - if (checkEndDeletes) { - await assertLineupEndUnused(ctx, existing, liveLinkEnds); - } const revision = existing.revision + 1; await ctx.db.patch(existing._id, { deleted: true, @@ -1547,14 +1412,6 @@ async function applyLineupOp( existing.pageId, ); } - if (checkLinkEnds && op.kind === "patch" && op.payload !== undefined) { - await assertLinkEndsLive( - ctx, - strategy._id, - existing.pageId, - op.payload as LineupPayload, - ); - } const revision = existing.revision + 1; await ctx.db.patch(existing._id, { ...patch, @@ -1642,14 +1499,6 @@ export const applyBatch = mutation({ strategyPublicId: v.string(), clientId: v.string(), ops: v.array(strategyOpValidator), - // Set by clients that send a link only once its origin and landing are - // sent (see assertLinkEndsLive). Older clients may send a link a batch - // ahead of its ends, so their links are not checked. - checkLineupLinkEnds: v.optional(v.boolean()), - // Set by clients that send an origin or landing delete only after the - // link deletes on its page (see assertLineupEndUnused). Older clients - // may send them in any order, so their deletes are not checked. - checkLineupEndDeletes: v.optional(v.boolean()), // Set by clients that can restore a deleted page (pages:restore): a // delete of content on a page in the trash is refused, to be sent again // once the page is back. Older clients get the no-op a deleted page's @@ -1677,7 +1526,6 @@ export const applyBatch = mutation({ // Images deleted elements showed, whose upload placeholders may go once // the whole batch has applied and nothing shows them any more. const placeholderCandidates = new Set(); - const liveLinkEnds = new LiveLinkEnds(); // Outcomes are per operation: accepted changes and visible rejections are // committed together by this single Convex transaction. One stale op must @@ -1765,14 +1613,8 @@ export const applyBatch = mutation({ ctx, strategy, op, - args.checkLineupLinkEnds === true, - args.checkLineupEndDeletes === true, args.checkTrashedPageDeletes === true, - liveLinkEnds, ); - if (result.status === "ack") { - liveLinkEnds.recordAccepted(op, result.eventPageId); - } } if (result.status === "ack" && op.entityType !== "strategy") { contentChanged = true; diff --git a/convex/pageTrash.test.ts b/convex/pageTrash.test.ts index bac15bf1..e8bf685a 100644 --- a/convex/pageTrash.test.ts +++ b/convex/pageTrash.test.ts @@ -11,6 +11,7 @@ import { CURRENT_CLOUD_PROTOCOL_VERSION } from "./lib/cloudProtocol"; import { PAGE_TRASH_RETENTION_MS } from "./lib/entities"; import { UNKNOWN_DISPLAY_NAME } from "./lib/profile"; import schema from "./schema"; +import { lineupPayload } from "./testContent.helpers"; import { modules } from "./test.setup"; const ensureCurrentUser = makeFunctionReference<"mutation">( @@ -107,30 +108,24 @@ function imagePayload(assetPublicId: string) { }; } +/// Two lineups thrown from one sova origin, the first showing an image. const lineupRows = [ { - lineupPublicId: "lineupOrigin:o", - payload: { - kind: "lineupOrigin" as const, - payloadVersion: 1, - data: { id: "o", agent: { id: "agent-o", type: "sova", lineUpID: "o" } }, - }, + lineupPublicId: "k", + payload: lineupPayload("k", { + originId: "o", + landingId: "l", + agentType: "sova", + images: [{ id: "k-shot" }], + }), }, { - lineupPublicId: "lineupLanding:l", - payload: { - kind: "lineupLanding" as const, - payloadVersion: 1, - data: { id: "l", ability: { id: "ability-l" } }, - }, - }, - { - lineupPublicId: "lineupLink:k", - payload: { - kind: "lineupLink" as const, - payloadVersion: 1, - data: { id: "k", originId: "o", landingId: "l", images: [{ id: "k-shot" }] }, - }, + lineupPublicId: "k2", + payload: lineupPayload("k2", { + originId: "o", + landingId: "l2", + agentType: "sova", + }), }, ]; @@ -368,7 +363,7 @@ describe("page trash", () => { expect(trashed?.deletedAt).toEqual(expect.any(Number)); const kept = await rowsOnPage(t, trashed!._id); expect(kept.elements).toHaveLength(3); - expect(kept.lineups).toHaveLength(3); + expect(kept.lineups).toHaveLength(2); expect(kept.contents).toHaveLength(1); expect(kept.references.map((reference) => reference.assetPublicId).sort()) .toEqual(["b-image", "k-shot"]); @@ -543,28 +538,24 @@ describe("page trash", () => { }, { type: "lineup.add", - lineupPublicId: "lineupLanding:l2", + lineupPublicId: "k3", pagePublicId: pageB, - payload: { - kind: "lineupLanding", - payloadVersion: 1, - data: { id: "l2", ability: { id: "ability-l2" } }, - }, + payload: lineupPayload("k3", { originId: "o", landingId: "l3" }), sortIndex: 3, }, { type: "lineup.patch", - lineupPublicId: "lineupLink:k", + lineupPublicId: "k", pagePublicId: pageB, payload: { - ...lineupRows[2]!.payload, - data: { ...lineupRows[2]!.payload.data, name: "edited" }, + ...lineupRows[0]!.payload, + data: { ...lineupRows[0]!.payload.data, name: "edited" }, }, expectedLineupRevision: 1, }, { type: "lineup.delete", - lineupPublicId: "lineupLink:k", + lineupPublicId: "k", pagePublicId: pageB, expectedLineupRevision: 1, }, @@ -785,7 +776,7 @@ describe("page trash", () => { }, { type: "lineup.delete", - lineupPublicId: "lineupLink:k", + lineupPublicId: "k", pagePublicId: pageB, expectedLineupRevision: 1, }, diff --git a/convex/referencedAssetIds.test.ts b/convex/referencedAssetIds.test.ts index 5ca9b7b0..f36049a4 100644 --- a/convex/referencedAssetIds.test.ts +++ b/convex/referencedAssetIds.test.ts @@ -9,6 +9,7 @@ import type { DataModel } from "./_generated/dataModel"; import { markAssetReferencesReady } from "./lib/assetReferences"; import { CURRENT_CLOUD_PROTOCOL_VERSION } from "./lib/cloudProtocol"; import schema from "./schema"; +import { lineupPayload } from "./testContent.helpers"; import { modules } from "./test.setup"; const ensureCurrentUser = makeFunctionReference<"mutation">( @@ -106,18 +107,9 @@ async function seed(t: RootHarness): Promise { }, { type: "lineup.add", - lineupPublicId: "lineupLink:k", + lineupPublicId: "k", pagePublicId, - payload: { - kind: "lineupLink" as const, - payloadVersion: 1, - data: { - id: "k", - originId: "o", - landingId: "l", - images: [{ id: "shot" }], - }, - }, + payload: lineupPayload("k", { images: [{ id: "shot" }] }), sortIndex: 0, }, ]); diff --git a/convex/schema.ts b/convex/schema.ts index fe3e1093..1247d0a1 100644 --- a/convex/schema.ts +++ b/convex/schema.ts @@ -105,9 +105,9 @@ export default defineSchema({ publicId: v.string(), strategyId: v.id("strategies"), pageId: v.id("pages"), - // Graph rows are keyed `:`; see ops.ts. A key is - // unique within its strategy, not across strategies: a copied strategy - // may reuse its original's entity ids. + // One row per lineup, keyed by the lineup's id (payload.data.id). A key + // is unique within its strategy, not across strategies: a strategy + // copied on a device keeps its original's lineup ids. payloadKind: lineupPayloadKindValidator, payloadVersion: v.number(), payload: lineupPayloadValidator, @@ -119,15 +119,8 @@ export default defineSchema({ }) .index("by_strategyId_and_publicId", ["strategyId", "publicId"]) .index("by_pageId", ["pageId"]) - .index("by_strategyId", ["strategyId"]) - .index("by_strategyId_and_payloadKind", ["strategyId", "payloadKind"]) - // Live links on a page, read when an origin or landing is deleted (see - // assertLineupEndUnused in ops.ts). - .index("by_pageId_and_payloadKind_and_deleted", [ - "pageId", - "payloadKind", - "deleted", - ]) + // Live lineups of a strategy, read for its agent summary. + .index("by_strategyId_and_deleted", ["strategyId", "deleted"]) .index("by_deleted_and_updatedAt", ["deleted", "updatedAt"]), // Which content rows show which images: one small row per (element or // lineup row, image id it shows), kept in step with every content write diff --git a/convex/strategies.ts b/convex/strategies.ts index 17c9fbb6..cae79d16 100644 --- a/convex/strategies.ts +++ b/convex/strategies.ts @@ -536,11 +536,11 @@ function isJsonObject(value: unknown): value is Record { type LineupPayload = Doc<"lineups">["payload"]; -/// New entity ids for a copied strategy's lineups: one map across every -/// kind, shared by every row. The source's lineups may share ids across -/// kinds (a landing may take its link's id), and the copy keeps that shape, -/// so a link still names its origin and landing whatever order the rows are -/// copied in. Keys stay unique through the kind prefix. +/// New ids for a copied strategy's lineups: one map shared by every row and +/// every id in it. Lineups that share an origin or landing in the source +/// carry the same id for it, so their copies do too, and the copy draws +/// them as one spot again. Ids the source repeats across a lineup and its +/// ends (a landing may take its lineup's id) repeat in the copy the same way. function lineupIdMap() { const ids = new Map(); return (id: string): string => { @@ -550,63 +550,39 @@ function lineupIdMap() { }; } -/// A lineup row as the copy stores it: its `:` key and -/// payload under new ids, with every nested reference following its entity. +/// A lineup row as the copy stores it: the lineup, its origin and its +/// landing under new ids, each marker's `lineUpID` following its end. function copiedLineupRow( payload: LineupPayload, newId: (id: string) => string, ): { publicId: string; payload: LineupPayload } { const data = payload.data; const idOf = (value: unknown) => (typeof value === "string" ? value : ""); - switch (payload.kind) { - case "lineupOrigin": { - const id = newId(idOf(data.id)); - const agent = data.agent; - return { - publicId: `lineupOrigin:${id}`, - payload: { - ...payload, - data: { - ...data, - id, - ...(isJsonObject(agent) ? { agent: { ...agent, lineUpID: id } } : {}), - } as LineupData, - }, - }; - } - case "lineupLanding": { - const id = newId(idOf(data.id)); - const ability = data.ability; - return { - publicId: `lineupLanding:${id}`, - payload: { - ...payload, - data: { - ...data, - id, - ...(isJsonObject(ability) - ? { ability: { ...ability, lineUpID: id } } - : {}), - } as LineupData, - }, - }; - } - case "lineupLink": { - const id = newId(idOf(data.id)); - return { - publicId: `lineupLink:${id}`, - payload: { - ...payload, - data: { - ...data, - id, - originId: newId(idOf(data.originId)), - landingId: newId(idOf(data.landingId)), - } as LineupData, - }, - }; - } - } + const copiedEnd = (end: unknown, markerKey: "agent" | "ability") => { + if (!isJsonObject(end)) return end; + const id = newId(idOf(end.id)); + const marker = end[markerKey]; + return { + ...end, + id, + ...(isJsonObject(marker) + ? { [markerKey]: { ...marker, lineUpID: id } } + : {}), + }; + }; + const id = newId(idOf(data.id)); + return { + publicId: id, + payload: { + ...payload, + data: { + ...data, + id, + origin: copiedEnd(data.origin, "agent"), + landing: copiedEnd(data.landing, "ability"), + } as LineupData, + }, + }; } // A duplicate is one transaction, and Convex refuses a transaction past @@ -792,8 +768,7 @@ export const duplicate = mutation({ ); } const copiedElements: ReferencingElement[] = []; - const copiedLineups: (ReferencingLineup & - Pick, "payloadKind">)[] = []; + const copiedLineups: ReferencingLineup[] = []; for (const element of sourceElements) { const pageId = pageIdMap.get(element.pageId); if (element.deleted || pageId === undefined) continue; diff --git a/convex/strategyDuplicate.test.ts b/convex/strategyDuplicate.test.ts index 627806cc..988db05a 100644 --- a/convex/strategyDuplicate.test.ts +++ b/convex/strategyDuplicate.test.ts @@ -9,6 +9,7 @@ import type { DataModel } from "./_generated/dataModel"; import { markAssetReferencesReady } from "./lib/assetReferences"; import { CURRENT_CLOUD_PROTOCOL_VERSION } from "./lib/cloudProtocol"; import schema from "./schema"; +import { lineupPayload } from "./testContent.helpers"; import { modules } from "./test.setup"; const ensureCurrentUser = makeFunctionReference<"mutation">( @@ -71,6 +72,18 @@ async function createHarness(): Promise<{ return { t, owner, other }; } +const seedLineupData = { + id: "item-1", + name: "Shock dart", + notes: "Two bounces", + images: [{ id: "lineup-image" }], + origin: { id: "origin-1", agent: { type: "sova", lineUpID: "origin-1" } }, + landing: { + id: "item-1", + ability: { type: "shock_dart", lineUpID: "item-1" }, + }, +}; + /// A two-page strategy with a placed image, a lineup with an image, an agent, /// and one deleted element, backed by active R2 asset rows. async function seedSource(t: RootHarness, owner: Harness): Promise { @@ -170,25 +183,20 @@ async function seedSource(t: RootHarness, owner: Harness): Promise { pagePublicId: firstPage, expectedElementRevision: 1, }, - // One lineup: an origin, a landing and the link between them (the - // landing shares its link's id), the link showing an image. - ...( - [ - ["lineupOrigin", { id: "origin-1", agent: { type: "sova", lineUpID: "origin-1" } }], - ["lineupLanding", { id: "item-1", ability: { type: "shock_dart", lineUpID: "item-1" } }], - ["lineupLink", { - id: "item-1", originId: "origin-1", landingId: "item-1", - images: [{ id: "lineup-image" }], - }], - ] as Array<[string, Record]> - ).map(([kind, data], index) => ({ - opId: `add-${kind}`, + // One lineup showing an image. Its landing shares the lineup's id, as + // landings made with their lineup do. + { + opId: "add-lineup", type: "lineup.add", - lineupPublicId: `${kind}:${data.id}`, + lineupPublicId: "item-1", pagePublicId: secondPage, - payload: { kind, payloadVersion: 1, data }, - sortIndex: index, - })), + payload: { + kind: "lineup", + payloadVersion: 1, + data: seedLineupData, + }, + sortIndex: 0, + }, ], }); } @@ -314,34 +322,27 @@ describe("strategies:duplicate", () => { expect(agent.publicId).not.toBe("placed-agent"); expect(agent.payload.data).toEqual({ id: agent.publicId, type: "jett" }); - expect(copy.lineups).toHaveLength(3); - for (const row of copy.lineups) { - expect(row.pagePublicId).toBe(copySecondPage); - } - const copied = (kind: string) => - copy.lineups.find((row) => row.payload.kind === kind)!.payload.data; - const origin = copied("lineupOrigin"); - const landing = copied("lineupLanding"); - const link = copied("lineupLink"); - // Fresh ids, still shared where the source shared them, with every - // reference following: the origin's agent, the landing's ability, and - // the link's ends. + expect(copy.lineups).toHaveLength(1); + const lineup = copy.lineups[0]!; + expect(lineup.pagePublicId).toBe(copySecondPage); + expect(lineup.payload.kind).toBe("lineup"); + // Fresh ids, still shared where the source shared them (the landing + // keeps the lineup's id), with each marker following its end. + const { id, origin, landing } = lineup.payload.data; + expect(lineup.publicId).toBe(id); + expect(id).not.toBe("item-1"); expect(origin.id).not.toBe("origin-1"); - expect(landing.id).not.toBe("item-1"); - expect(link.id).toBe(landing.id); - expect(origin).toEqual({ - id: origin.id, - agent: { type: "sova", lineUpID: origin.id }, - }); - expect(landing).toEqual({ - id: landing.id, - ability: { type: "shock_dart", lineUpID: landing.id }, - }); - expect(link).toEqual({ - id: link.id, - originId: origin.id, - landingId: landing.id, + expect(landing.id).toBe(id); + expect(lineup.payload.data).toEqual({ + id, + name: "Shock dart", + notes: "Two bounces", images: [{ id: "lineup-image" }], + origin: { id: origin.id, agent: { type: "sova", lineUpID: origin.id } }, + landing: { + id, + ability: { type: "shock_dart", lineUpID: id }, + }, }); expect(await imageUrls(owner, "duplicate-copy")).toEqual({ @@ -407,7 +408,7 @@ describe("strategies:duplicate", () => { ).toEqual([]); }); - test("a fan-in lineup graph copies whole, with its link image, and outlives the original", async () => { + test("lineups that share spots copy with their sharing, details and image, and outlive the original", async () => { vi.useFakeTimers(); const fetchMock = mockR2Deletes(); const { t, owner } = await createHarness(); @@ -435,31 +436,39 @@ describe("strategies:duplicate", () => { updatedAt: now, }); }); - // Two origins into one landing. The landing shares an id with a link, - // as landings made before the graph synced natively do. - const rows: Array<[string, Record]> = [ - ["lineupOrigin", { id: "origin-a", agent: { type: "sova", lineUpID: "origin-a" } }], - ["lineupOrigin", { id: "origin-b", agent: { type: "sova", lineUpID: "origin-b" } }], - ["lineupLanding", { id: "link-a", ability: { type: "shock_dart", lineUpID: "link-a" } }], - ["lineupLink", { - id: "link-a", originId: "origin-a", landingId: "link-a", - name: "From heaven", images: [{ id: "link-image", fileExtension: ".png" }], - }], - ["lineupLink", { - id: "link-b", originId: "origin-b", landingId: "link-a", - name: "From mid", images: [], - }], + // Two lineups from different origins into one landing (fan-in), and a + // third from the first one's origin to another landing (fan-out). The + // shared landing takes the first lineup's id, as a landing made with its + // lineup does. + const rows = [ + lineupPayload("link-a", { + originId: "origin-a", + landingId: "link-a", + name: "From heaven", + notes: "Run and throw", + images: [{ id: "link-image", fileExtension: ".png" }], + }), + lineupPayload("link-b", { + originId: "origin-b", + landingId: "link-a", + name: "From mid", + }), + lineupPayload("link-c", { + originId: "origin-a", + landingId: "landing-c", + name: "Fan out", + }), ]; const added = (await owner.mutation(applyBatch, { ...protocol, strategyPublicId: source, - clientId: "graph", - ops: rows.map(([kind, data], index) => ({ - opId: `add-${kind}-${data.id}`, + clientId: "shared-spots", + ops: rows.map((payload, index) => ({ + opId: `add-${payload.data.id}`, type: "lineup.add", - lineupPublicId: `${kind}:${data.id}`, + lineupPublicId: payload.data.id, pagePublicId: firstPage, - payload: { kind, payloadVersion: 1, data }, + payload, sortIndex: 10 + index, })), })) as { results: Array<{ status: string }> }; @@ -473,48 +482,81 @@ describe("strategies:duplicate", () => { type Row = { publicId: string; pagePublicId: string; + sortIndex: number; payload: { kind: string; data: Record }; }; const copy = (await owner.query(getFullSnapshot, { ...protocol, strategyPublicId: "duplicate-copy", })) as { pages: Array<{ publicId: string; sortIndex: number }>; lineups: Row[] }; - // The fan-in sits on the first page (the seed's lineup is on the second). + // These sit on the first page (the seed's lineup is on the second). const copyFirstPage = [...copy.pages].sort((a, b) => a.sortIndex - b.sortIndex)[0]! .publicId; - const graph = copy.lineups.filter((row) => row.pagePublicId === copyFirstPage); - // Every graph row is keyed by its kind and its new entity id. - for (const row of graph) { - expect(row.publicId).toBe(`${row.payload.kind}:${row.payload.data.id}`); - } - const ofKind = (kind: string) => - graph.filter((row) => row.payload.kind === kind).map((row) => row.payload.data); - const origins = ofKind("lineupOrigin"); - const landings = ofKind("lineupLanding"); - const links = ofKind("lineupLink"); - expect(origins).toHaveLength(2); - expect(landings).toHaveLength(1); - expect(links).toHaveLength(2); - const oldIds = ["origin-a", "origin-b", "link-a", "link-b"]; - for (const entity of [...origins, ...landings, ...links]) { - expect(oldIds).not.toContain(entity.id); - } - for (const origin of origins) expect(origin.agent.lineUpID).toBe(origin.id); - expect(landings[0]!.ability.lineUpID).toBe(landings[0]!.id); - // Fan-in survives: both links name the one copied landing and their own - // copied origins, with their names and image. - const originIds = origins.map((origin) => origin.id); - expect(links.map((link) => [link.name, link.landingId])).toEqual([ - ["From heaven", landings[0]!.id], - ["From mid", landings[0]!.id], + const copied = copy.lineups + .filter((row) => row.pagePublicId === copyFirstPage) + .sort((a, b) => a.sortIndex - b.sortIndex); + expect(copied.map((row) => row.payload.data.name)).toEqual([ + "From heaven", + "From mid", + "Fan out", ]); - expect(links.map((link) => originIds.indexOf(link.originId))).toEqual([0, 1]); - expect(links[0]!.images).toEqual([{ id: "link-image", fileExtension: ".png" }]); + const oldIds = [ + "link-a", + "link-b", + "link-c", + "origin-a", + "origin-b", + "landing-c", + ]; + for (const row of copied) { + const { id, origin, landing } = row.payload.data; + // Each row is keyed by its new lineup id. + expect(row.publicId).toBe(id); + for (const newId of [id, origin.id, landing.id]) { + expect(oldIds).not.toContain(newId); + } + // Each marker follows its end's new id. + expect(origin.agent.lineUpID).toBe(origin.id); + expect(landing.ability.lineUpID).toBe(landing.id); + } + const [heaven, mid, fanOut] = copied.map((row) => row.payload.data); + // Fan-in survives: one new landing id, still the first lineup's id. + expect(mid!.landing.id).toBe(heaven!.landing.id); + expect(heaven!.landing.id).toBe(heaven!.id); + // Fan-out survives: one new origin id for both lineups from it. + expect(fanOut!.origin.id).toBe(heaven!.origin.id); + // And spots that were apart stay apart. + expect(mid!.origin.id).not.toBe(heaven!.origin.id); + expect(fanOut!.landing.id).not.toBe(heaven!.landing.id); + expect(new Set(copied.map((row) => row.publicId)).size).toBe(3); + // Details and the image come along. + expect(heaven).toMatchObject({ + notes: "Run and throw", + youtubeLink: "", + images: [{ id: "link-image", fileExtension: ".png" }], + }); + expect(mid!.images).toEqual([]); - // The copy's link image is its own reference and survived the original. + // The copy's lineup image is its own reference and survived the + // original. expect(fetchMock).not.toHaveBeenCalled(); const urls = await imageUrls(owner, "duplicate-copy"); expect(urls["link-image"]).toEqual(expect.stringContaining("link-image.png")); + const lineupReferences = await t.run(async (ctx) => { + const copyRow = await ctx.db + .query("strategies") + .withIndex("by_publicId", (q) => q.eq("publicId", "duplicate-copy")) + .unique(); + return (await ctx.db.query("assetReferences").collect()) + .filter( + (reference) => + reference.strategyId === copyRow!._id && + reference.lineupId !== undefined, + ) + .map((reference) => reference.assetPublicId) + .sort(); + }); + expect(lineupReferences).toEqual(["lineup-image", "link-image"]); await deleteAndSweep(t, owner, "duplicate-copy"); expect(deletedKeys(fetchMock)).toContain( @@ -600,8 +642,8 @@ describe("strategies:duplicate", () => { .filter((row) => row.publicId === "placed-image") .map((row) => row.uploadStatus); - // The placed image is deleted; a month later a lineup link shows the - // same image and is deleted too, so undo could still restore it. + // The placed image is deleted; a month later a lineup shows the same + // image and is deleted too, so undo could still restore it. await owner.mutation(applyBatch, { ...protocol, strategyPublicId: source, @@ -617,16 +659,9 @@ describe("strategies:duplicate", () => { ], }); vi.setSystemTime(Date.now() + 31 * day); - const showing = { - kind: "lineupLink", - payloadVersion: 1, - data: { - id: "late-link", - originId: "o", - landingId: "l", - images: [{ id: "placed-image" }], - }, - }; + const showing = lineupPayload("late-link", { + images: [{ id: "placed-image" }], + }); await owner.mutation(applyBatch, { ...protocol, strategyPublicId: source, @@ -635,7 +670,7 @@ describe("strategies:duplicate", () => { { opId: "add-late-link", type: "lineup.add", - lineupPublicId: "lineupLink:late-link", + lineupPublicId: "late-link", pagePublicId: firstPage, payload: showing, sortIndex: 9, @@ -643,14 +678,14 @@ describe("strategies:duplicate", () => { { opId: "delete-late-link", type: "lineup.delete", - lineupPublicId: "lineupLink:late-link", + lineupPublicId: "late-link", pagePublicId: firstPage, expectedLineupRevision: 1, }, ], }); - // Purging the old element tombstone keeps the image: the link's + // Purging the old element tombstone keeps the image: the lineup's // tombstone is still inside its retention window. await t.mutation(purgeOldTombstones, {}); await t.finishAllScheduledFunctions(vi.runAllTimers); @@ -1084,7 +1119,7 @@ describe("images placed before their upload", () => { expect(rows[0]).toMatchObject({ uploadStatus: "active" }); }); - test("deleting an image keeps the placeholder while a lineup link still shows it", async () => { + test("deleting an image keeps the placeholder while a lineup still shows it", async () => { const { t, owner } = await createHarness(); await seedSource(t, owner); await placeImage(owner, "link-late-image"); @@ -1096,18 +1131,11 @@ describe("images placed before their upload", () => { { opId: "link-shows-shared", type: "lineup.add", - lineupPublicId: "lineupLink:link-shows-shared", + lineupPublicId: "link-shows-shared", pagePublicId: firstPage, - payload: { - kind: "lineupLink", - payloadVersion: 1, - data: { - id: "link-shows-shared", - originId: "o", - landingId: "l", - images: [{ id: "link-late-image", fileExtension: ".png" }], - }, - }, + payload: lineupPayload("link-shows-shared", { + images: [{ id: "link-late-image", fileExtension: ".png" }], + }), sortIndex: 2, }, ], @@ -1144,18 +1172,11 @@ describe("images placed before their upload", () => { { opId: "link-shows-b", type: "lineup.add", - lineupPublicId: "lineupLink:shows-b", + lineupPublicId: "shows-b", pagePublicId: firstPage, - payload: { - kind: "lineupLink", - payloadVersion: 1, - data: { - id: "shows-b", - originId: "o", - landingId: "l", - images: [{ id: "pending-b", fileExtension: ".png" }], - }, - }, + payload: lineupPayload("shows-b", { + images: [{ id: "pending-b", fileExtension: ".png" }], + }), sortIndex: 5, }, { @@ -1195,18 +1216,11 @@ describe("images placed before their upload", () => { { opId: "lineup-shows-batch", type: "lineup.add", - lineupPublicId: "lineupLink:lineup-shows-batch", + lineupPublicId: "lineup-shows-batch", pagePublicId: firstPage, - payload: { - kind: "lineupLink", - payloadVersion: 1, - data: { - id: "lineup-shows-batch", - originId: "o", - landingId: "l", - images: [{ id: "batch-shown" }], - }, - }, + payload: lineupPayload("lineup-shows-batch", { + images: [{ id: "batch-shown" }], + }), sortIndex: 3, }, ], @@ -1229,7 +1243,7 @@ describe("images placed before their upload", () => { expect(await rowsFor(t, "batch-alone")).toEqual([]); }); - test("a lineup link's image referenced before its upload is expected too", async () => { + test("a lineup's image referenced before its upload is expected too", async () => { const { t, owner } = await createHarness(); await seedSource(t, owner); await owner.mutation(applyBatch, { @@ -1240,18 +1254,11 @@ describe("images placed before their upload", () => { { opId: "late-link", type: "lineup.add", - lineupPublicId: "lineupLink:late-link", + lineupPublicId: "late-link", pagePublicId: firstPage, - payload: { - kind: "lineupLink", - payloadVersion: 1, - data: { - id: "late-link", - originId: "o", - landingId: "l", - images: [{ id: "late-link-image", fileExtension: ".png" }], - }, - }, + payload: lineupPayload("late-link", { + images: [{ id: "late-link-image", fileExtension: ".png" }], + }), sortIndex: 1, }, ], diff --git a/convex/syncBoundaries.test.ts b/convex/syncBoundaries.test.ts index f7944f2e..0010f69c 100644 --- a/convex/syncBoundaries.test.ts +++ b/convex/syncBoundaries.test.ts @@ -15,7 +15,11 @@ import { serializedConvexValueUtf8Bytes, } from "./lib/cloudProtocol"; import schema from "./schema"; -import { insertElement, insertLineup } from "./testContent.helpers"; +import { + insertElement, + insertLineup, + lineupPayload, +} from "./testContent.helpers"; import { modules } from "./test.setup"; const ensureCurrentUser = makeFunctionReference<"mutation">( @@ -75,19 +79,14 @@ function imagePayload(assetPublicId: string) { }; } -/// A lineup link showing one image; its row key is `lineupLink:`. -function lineupPayload(assetPublicId: string, linkId = "restorable") { - return { - kind: "lineupLink" as const, - payloadVersion: 1, - data: { - id: linkId, - originId: "origin", - landingId: "landing", - name: "B lineup", - images: [{ id: assetPublicId }], - }, - }; +/// A lineup showing one image; its row key is [lineupId]. +function lineupShowing(assetPublicId: string, lineupId = "restorable") { + return lineupPayload(lineupId, { + originId: "origin", + landingId: "landing", + name: "B lineup", + images: [{ id: assetPublicId }], + }); } /** A ConvexError's code (convex-test passes its data as JSON text). */ @@ -353,12 +352,12 @@ async function seedTwoPageContent(t: Harness, owner: Harness) { updatedAt: now, }); await insertLineup(ctx, { - publicId: "lineupLink:lineup-b", + publicId: "lineup-b", strategyId: strategy._id, pageId: pageBId, - payloadKind: "lineupLink", + payloadKind: "lineup", payloadVersion: 1, - payload: lineupPayload(assetB, "lineup-b"), + payload: lineupShowing(assetB, "lineup-b"), sortIndex: 0, revision: 1, deleted: false, @@ -437,7 +436,7 @@ describe("page-scoped read contract", () => { "element-b", ]); expect(snapshot.lineups.map((item) => item.publicId)).toEqual([ - "lineupLink:lineup-b", + "lineup-b", ]); expect(snapshot.assets.map((item) => item.publicId).sort()).toEqual([ "asset-a", @@ -1064,7 +1063,7 @@ describe("record-scoped write contract", () => { test("soft-deleted elements and lineups can be restored with their ids", async () => { const elementId = "restorable-element"; - const lineupId = "lineupLink:restorable"; + const lineupId = "restorable"; const restoredText = "restored"; const restoredAsset = "restored-asset"; const { owner } = await createHarness(); @@ -1084,7 +1083,7 @@ describe("record-scoped write contract", () => { entityType: "lineup", entityPublicId: lineupId, pagePublicId: pageA, - payload: lineupPayload("before-delete-asset"), + payload: lineupShowing("before-delete-asset"), }, ]); await applyOps(owner, "undo-restore", [ @@ -1119,7 +1118,7 @@ describe("record-scoped write contract", () => { entityType: "lineup", entityPublicId: lineupId, pagePublicId: pageA, - payload: lineupPayload(restoredAsset), + payload: lineupShowing(restoredAsset), }, ]); expect(missingRevision.results).toMatchObject([ @@ -1151,7 +1150,7 @@ describe("record-scoped write contract", () => { entityType: "lineup", entityPublicId: lineupId, pagePublicId: pageA, - payload: lineupPayload(restoredAsset), + payload: lineupShowing(restoredAsset), expectedRevision: 1, }, ]); @@ -1185,7 +1184,7 @@ describe("record-scoped write contract", () => { entityType: "lineup", entityPublicId: lineupId, pagePublicId: pageA, - payload: lineupPayload("before-delete-asset"), + payload: lineupShowing("before-delete-asset"), sortIndex: 0, expectedRevision: 2, }, @@ -1225,7 +1224,7 @@ describe("record-scoped write contract", () => { entityType: "lineup", entityPublicId: lineupId, pagePublicId: pageA, - payload: lineupPayload(restoredAsset), + payload: lineupShowing(restoredAsset), expectedRevision: 2, }, ]); @@ -1389,30 +1388,30 @@ describe("record-scoped write contract", () => { expect(replayed).toMatchObject({ revision: 2, reused: true }); }); - test("a lineup link without its landing is refused", async () => { + test("a lineup without its landing is refused", async () => { const { owner } = await createHarness(); await createBaseStrategy(owner); + const { landing: _landing, ...withoutLanding } = lineupShowing( + "asset-empty", + "lineup-empty", + ).data; const response = await applyOps(owner, "empty-lineup", [ { opId: "add-empty-lineup", kind: "add", entityType: "lineup", - entityPublicId: "lineupLink:lineup-empty", + entityPublicId: "lineup-empty", pagePublicId: pageA, - payload: { - kind: "lineupLink" as const, - payloadVersion: 1, - data: { id: "lineup-empty", originId: "origin" }, - }, + payload: { kind: "lineup", payloadVersion: 1, data: withoutLanding }, }, { opId: "add-lineup", kind: "add", entityType: "lineup", - entityPublicId: "lineupLink:lineup-full", + entityPublicId: "lineup-full", pagePublicId: pageA, - payload: lineupPayload("asset-full", "lineup-full"), + payload: lineupShowing("asset-full", "lineup-full"), }, ]); @@ -1427,26 +1426,43 @@ describe("record-scoped write contract", () => { }); }); - test("a legacy lineup group is no longer part of the contract", async () => { + test("old lineup shapes are no longer part of the contract", async () => { const { owner } = await createHarness(); await createBaseStrategy(owner); - await expect( - applyOps(owner, "old-client", [ + // A protocol 4 graph row (an origin, landing or link) and the legacy + // group before it are both refused whole by argument validation. + for (const [entityPublicId, payload] of [ + [ + "lineupLink:k", { - opId: "add-legacy-group", - kind: "add", - entityType: "lineup", - entityPublicId: "legacy-group", - pagePublicId: pageA, - payload: { - kind: "lineupGroup", - payloadVersion: 1, - data: { id: "legacy-group", items: [{ id: "item" }] }, - }, + kind: "lineupLink", + payloadVersion: 1, + data: { id: "k", originId: "o", landingId: "l", images: [] }, + }, + ], + [ + "legacy-group", + { + kind: "lineupGroup", + payloadVersion: 1, + data: { id: "legacy-group", items: [{ id: "item" }] }, }, - ]), - ).rejects.toThrow(/lineupGroup|ArgumentValidationError|Validator/); + ], + ] as const) { + await expect( + applyOps(owner, "old-client", [ + { + opId: `add-${entityPublicId}`, + kind: "add", + entityType: "lineup", + entityPublicId, + pagePublicId: pageA, + payload, + }, + ]), + ).rejects.toThrow(/Validator error|ArgumentValidationError/); + } const snapshot = (await owner.query(getPageSnapshot, { clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, strategyPublicId, diff --git a/convex/testContent.helpers.ts b/convex/testContent.helpers.ts index 7e827ff1..18714806 100644 --- a/convex/testContent.helpers.ts +++ b/convex/testContent.helpers.ts @@ -27,3 +27,52 @@ export async function insertLineup( await syncLineupAssetReferences(ctx, id, lineup); return id; } + +export type TestLineup = { + originId?: string; + landingId?: string; + agentType?: string; + originPosition?: { dx: number; dy: number }; + landingPosition?: { dx: number; dy: number }; + name?: string; + youtubeLink?: string; + notes?: string; + images?: Array<{ id: string; fileExtension?: string }>; +}; + +/// A lineup row's payload as a client writes it: the lineup's details and +/// whole copies of its origin (the agent) and landing (the ability), each +/// marker's `lineUpID` naming its end. Lineups that share a spot pass the +/// same originId or landingId. The row's key is [id]. +export function lineupPayload(id: string, lineup: TestLineup = {}) { + const originId = lineup.originId ?? `${id}-origin`; + const landingId = lineup.landingId ?? `${id}-landing`; + return { + kind: "lineup" as const, + payloadVersion: 1, + data: { + id, + name: lineup.name ?? "", + youtubeLink: lineup.youtubeLink ?? "", + notes: lineup.notes ?? "", + images: lineup.images ?? [], + origin: { + id: originId, + agent: { + id: `agent-${originId}`, + type: lineup.agentType ?? "sova", + position: lineup.originPosition ?? { dx: 0, dy: 0 }, + lineUpID: originId, + }, + }, + landing: { + id: landingId, + ability: { + id: `ability-${landingId}`, + position: lineup.landingPosition ?? { dx: 0, dy: 0 }, + lineUpID: landingId, + }, + }, + }, + }; +} From a4a7bfddc39158def778407d5aa07a92a7426d1d Mon Sep 17 00:00:00 2001 From: Dara Adedeji Date: Thu, 1 Oct 2026 19:28:46 -0400 Subject: [PATCH 02/14] Sync each lineup as one row and draw shared spots from the copies The client side of one row per lineup. Live sync turns the page's graph into one desired row per link, each carrying its origin and landing as they are now, so moving a shared spot patches every lineup on it, each as its own op against its own revision, and deleting a spot deletes the rows of the lineups on it. Hydration rebuilds the graph from the rows: links from rows, origins and landings deduplicated by id. When copies of one spot disagree (only after concurrent edits) every client draws the copy from the row with the highest revision, ties to the greatest publicId, and live sync compares rows in that drawn form so the choice never authors a write. The user's own pending rows outrank the server until they land. Removed with the split rows: kind-prefixed keys and the drawn-row join, held-back and unsyncable lineups, endpoint restore, undrawn remote rows, endpoint-first and link-first outbox scheduling, the missing-end Keep mine path and its messages, the sync status's unsyncable source, the graph id rewrite on upload, and the Paranoia correction for landings (lineup rows are written at the in-game size and are never corrected). A remote merge no longer holds the whole graph while the user holds any part of it: only lineups touching what is held, closed over the spots they share, keep their screen copy; the rest of the page's lineups update. An outbox record from an older build can still hold an origin, landing or link op. It is never sent: it waits in attention with its own reason until the user discards it, and Keep mine leaves it there. The local model, UI, Hive, .ica and undo are unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) --- lib/collab/cloud_lineup_rows.dart | 278 ++-- lib/collab/cloud_payload_upgrade.dart | 71 +- lib/collab/cloud_sync_error_message.dart | 16 +- lib/collab/collab_models.dart | 39 +- lib/collab/convex_payload_codecs.dart | 38 +- lib/collab/convex_strategy_repository.dart | 6 - lib/collab/generated/convex_error_codes.dart | 2 - lib/collab/generated/convex_models.dart | 261 +--- lib/collab/generated/icarus_convex_api.dart | 6 - lib/const/line_provider.dart | 10 + .../collab/active_page_live_sync_models.dart | 4 + .../active_page_live_sync_provider.dart | 180 +-- .../collab/cloud_sync_status_provider.dart | 4 - .../collab/strategy_op_queue_provider.dart | 176 +-- lib/strategy/remote_page_merge.dart | 78 ++ lib/strategy/strategy_cloud_migration.dart | 30 +- lib/strategy/strategy_import_export.dart | 14 +- lib/strategy/strategy_page_apply.dart | 28 +- lib/strategy/strategy_page_source.dart | 14 +- test/cloud_lineup_rows_test.dart | 234 +++- test/cloud_payload_upgrade_test.dart | 42 +- .../collab/cloud_sync_error_message_test.dart | 32 +- test/collab_sync_models_test.dart | 42 +- test/global_strategy_outbox_test.dart | 75 +- test/remote_page_merge_test.dart | 122 ++ test/strategy_cloud_migration_test.dart | 75 +- test/strategy_op_queue_provider_test.dart | 625 ++++----- test/strategy_page_session_provider_test.dart | 1119 ++++++++++------- test/unsaved_strategy_guard_test.dart | 4 +- test/widgets/cloud_sync_button_test.dart | 37 +- .../runtime/lib/workload.dart | 49 +- 31 files changed, 1789 insertions(+), 1922 deletions(-) diff --git a/lib/collab/cloud_lineup_rows.dart b/lib/collab/cloud_lineup_rows.dart index cad520f3..ae8a50f2 100644 --- a/lib/collab/cloud_lineup_rows.dart +++ b/lib/collab/cloud_lineup_rows.dart @@ -1,170 +1,164 @@ import 'package:icarus/collab/collab_models.dart'; import 'package:icarus/const/line_provider.dart'; -/// The payload kinds of the cloud rows that store a page's lineups. +/// One cloud row per lineup (a [LineUpLink]): its name, video, notes and +/// images, and whole copies of its origin and landing. /// -/// A page's lineups are a graph, and the cloud stores it one row per entity -/// so that teammates editing different links, origins or landings never -/// touch the same row. -abstract final class CloudLineupKind { - static const origin = 'lineupOrigin'; - static const landing = 'lineupLanding'; - static const link = 'lineupLink'; -} - -/// One lineup row: an origin, a landing or a link. +/// Lineups that share a spot (several lineups from one origin, or into one +/// landing) each carry their own copy of it, under the spot's id. The server +/// never joins rows, so every row can be drawn on its own. The client draws +/// the copies of one id as one spot again ([lineUpGraphFromCloudRows]), and +/// changing a shared spot changes every row that carries it +/// ([cloudLineupRows]). class CloudLineupRow { - const CloudLineupRow({required this.publicId, required this.payload}); + const CloudLineupRow({ + required this.publicId, + required this.payload, + this.revision = 0, + }); + + CloudLineupRow.remote(RemoteLineup lineup) + : this( + publicId: lineup.publicId, + payload: lineup.payload, + revision: lineup.revision, + ); - /// `:`. Entity ids repeat across kinds (a landing - /// made with its link shares the link's id, and lineups from 3.x used one - /// id for all three), so the kind is part of the key. The entity's own id - /// is `payload.data.id`, exactly as on the canvas. + /// The lineup's id, as on the canvas and in `payload.data.id`. final String publicId; final CloudPayload payload; + + /// The row's server revision, which picks the copy drawn when the copies + /// of a shared spot disagree. Zero for a row the server does not have. + final int revision; } -String cloudLineupRowId(String kind, String entityId) => '$kind:$entityId'; +/// The revision a lineup still waiting to be sent ranks at: above any the +/// server holds, so the user's own change to a shared spot is the copy drawn +/// until it lands. +const pendingCloudLineupRevision = 1 << 52; -/// The rows that store [graph]: its origins, then its landings, then its -/// links. +/// The rows that store [graph]: one per link, in link order, each carrying +/// its origin and landing as they are now. A link whose origin or landing is +/// missing draws nothing on the canvas, so it has no row either. List cloudLineupRows(LineUpGraph graph) { - CloudLineupRow row(String kind, String id, Map data) { - return CloudLineupRow( - publicId: cloudLineupRowId(kind, id), - payload: cloudLineupPayload(kind: kind, data: data), - ); - } - + final origins = {for (final origin in graph.origins) origin.id: origin}; + final landings = {for (final landing in graph.landings) landing.id: landing}; return [ - for (final origin in graph.origins) - row(CloudLineupKind.origin, origin.id, origin.toJson()), - for (final landing in graph.landings) - row(CloudLineupKind.landing, landing.id, landing.toJson()), for (final link in graph.links) - row(CloudLineupKind.link, link.id, link.toJson()), + if ((origins[link.originId], landings[link.landingId]) + case (final origin?, final landing?)) + CloudLineupRow( + publicId: link.id, + payload: cloudLineupPayload({ + ...link.toJson() + ..remove('originId') + ..remove('landingId'), + 'origin': origin.toJson(), + 'landing': landing.toJson(), + }), + ), ]; } -/// A page's lineup graph as its cloud rows describe it. -class CloudLineupGraph { - const CloudLineupGraph({required this.graph, required this.drawnRowIds}); - - final LineUpGraph graph; - - /// The rows [graph] was built from. Hydration draws only whole lineups: a - /// link whose origin and landing both exist, and the nodes such links use. - /// A row outside that (a link that arrived before its landing, an origin a - /// teammate's delete left without links) is never shown, so a client must - /// never author a change to it from what it hydrated. - final Set drawnRowIds; -} - -/// Reads a page's live lineup rows, in their stored order, into the graph. +/// A page's lineups as its live [rows] describe them. +/// +/// Links come from the rows, in their order. Origins and landings are the +/// rows' copies, one per id, in the order the rows first carry them. Copies +/// of one spot disagree only after teammates changed it at the same time; +/// every client then draws the copy from the row with the highest revision, +/// a tie going to the greatest publicId, so all of them show the same spot. /// -/// Throws a [FormatException] naming the row when one cannot be read, -/// including a row of any kind other than origin, landing or link. -CloudLineupGraph lineUpGraphFromCloudRows(Iterable rows) { - final origins = {}; - final landings = {}; - final links = {}; +/// Throws a [FormatException] naming the row when one cannot be read. +LineUpGraph lineUpGraphFromCloudRows(Iterable rows) { + final origins = {}; + final landings = {}; + final links = []; + bool outranks(CloudLineupRow row, CloudLineupRow? drawn) => + drawn == null || + row.revision > drawn.revision || + (row.revision == drawn.revision && + row.publicId.compareTo(drawn.publicId) > 0); for (final row in rows) { - final data = cloudPayloadData(row.payload); - try { - switch (row.payload['kind']) { - case CloudLineupKind.origin: - final origin = LineUpOrigin.fromJson(data); - origins[origin.id] = (origin, row.publicId); - case CloudLineupKind.landing: - final landing = LineUpLanding.fromJson(data); - landings[landing.id] = (landing, row.publicId); - case CloudLineupKind.link: - final link = LineUpLink.fromJson(data); - links[link.id] = (link, row.publicId); - case final kind: - throw FormatException('unknown lineup kind $kind'); - } - } catch (error, stackTrace) { - Error.throwWithStackTrace( - FormatException('Cloud lineup ${row.publicId} could not be read: ' - '$error'), - stackTrace, - ); + final (link, origin, landing) = _readLineupRow(row); + links.add(link); + if (outranks(row, origins[origin.id]?.$2)) { + origins[origin.id] = (origin, row); + } + if (outranks(row, landings[landing.id]?.$2)) { + landings[landing.id] = (landing, row); } } - - final drawnLinks = [ - for (final entry in links.values) - if (origins.containsKey(entry.$1.originId) && - landings.containsKey(entry.$1.landingId)) - entry, - ]; - final usedOrigins = {for (final (link, _) in drawnLinks) link.originId}; - final usedLandings = {for (final (link, _) in drawnLinks) link.landingId}; - final drawnOrigins = [ - for (final entry in origins.values) - if (usedOrigins.contains(entry.$1.id)) entry, - ]; - final drawnLandings = [ - for (final entry in landings.values) - if (usedLandings.contains(entry.$1.id)) entry, - ]; - - return CloudLineupGraph( - graph: LineUpGraph( - origins: [for (final (origin, _) in drawnOrigins) origin], - landings: [for (final (landing, _) in drawnLandings) landing], - links: [for (final (link, _) in drawnLinks) link], - ), - drawnRowIds: { - for (final (_, rowId) in drawnOrigins) rowId, - for (final (_, rowId) in drawnLandings) rowId, - for (final (_, rowId) in drawnLinks) rowId, - }, + return LineUpGraph( + origins: [for (final (origin, _) in origins.values) origin], + landings: [for (final (landing, _) in landings.values) landing], + links: links, ); } -/// [graph] with every origin, landing and link id passed through [newId], -/// and every reference to them (a link's ends, the `lineUpID` of the agent -/// and ability) following along. Used where a copy must not collide with -/// the rows it came from. -LineUpGraph lineUpGraphWithIds( - LineUpGraph graph, - String Function(String kind, String id) newId, +/// A page's lineups as its [lineups] on the server describe them. Deleted +/// rows draw nothing. +LineUpGraph lineUpGraphFromRemoteLineups(Iterable lineups) { + return lineUpGraphFromCloudRows([ + for (final lineup in lineups) + if (!lineup.deleted) CloudLineupRow.remote(lineup), + ]); +} + +/// The payloads of [rows] as the canvas draws them, by publicId: each row +/// rebuilt from [lineUpGraphFromCloudRows], so a row whose copy of a shared +/// spot is not the one drawn carries the drawn copy. Comparing these with +/// the canvas never mistakes the copy hydration chose for a local edit. +Map drawnCloudLineupPayloads( + Iterable rows, ) { - final originIds = { - for (final origin in graph.origins) - origin.id: newId(CloudLineupKind.origin, origin.id), - }; - final landingIds = { - for (final landing in graph.landings) - landing.id: newId(CloudLineupKind.landing, landing.id), + return { + for (final row in cloudLineupRows(lineUpGraphFromCloudRows(rows))) + row.publicId: row.payload, }; - return LineUpGraph( - origins: [ - for (final origin in graph.origins) - LineUpOrigin( - id: originIds[origin.id]!, - agent: origin.agent.copyWith(lineUpID: originIds[origin.id]!) - ..isDeleted = origin.agent.isDeleted, - ), - ], - landings: [ - for (final landing in graph.landings) - LineUpLanding( - id: landingIds[landing.id]!, - ability: landing.ability.copyWith(lineUpID: landingIds[landing.id]!) - ..isDeleted = landing.ability.isDeleted, - ), - ], - links: [ - for (final link in graph.links) - link.copyWith( - id: newId(CloudLineupKind.link, link.id), - originId: originIds[link.originId] ?? link.originId, - landingId: landingIds[link.landingId] ?? link.landingId, - ), - ], - ); +} + +/// Whether [op] is a lineup change in the cloud format before one row per +/// lineup: an origin, landing or link row keyed `:`. An outbox +/// written by such a build can still hold one; no server takes it. +bool isRetiredCloudLineupOp(StrategyOp op) { + if (op.entityType != StrategyOpEntityType.lineup) return false; + final payload = op.payload; + return (payload is Map && payload['kind'] != cloudLineupPayloadKind) || + _retiredLineupKey.hasMatch(op.entityPublicId ?? ''); +} + +final _retiredLineupKey = RegExp(r'^lineup(Origin|Landing|Link):'); + +(LineUpLink, LineUpOrigin, LineUpLanding) _readLineupRow(CloudLineupRow row) { + try { + final kind = row.payload['kind']; + if (kind != cloudLineupPayloadKind) { + throw FormatException('unknown lineup kind $kind'); + } + final data = cloudPayloadData(row.payload); + final origin = LineUpOrigin.fromJson(_object(data['origin'], 'origin')); + final landing = LineUpLanding.fromJson(_object(data['landing'], 'landing')); + final link = LineUpLink.fromJson({ + ...data, + 'originId': origin.id, + 'landingId': landing.id, + }); + if (link.id != row.publicId) { + throw FormatException('it holds lineup ${link.id}'); + } + return (link, origin, landing); + } catch (error, stackTrace) { + Error.throwWithStackTrace( + FormatException('Cloud lineup ${row.publicId} could not be read: ' + '$error'), + stackTrace, + ); + } +} + +Map _object(Object? value, String name) { + if (value is! Map) throw FormatException('it has no $name'); + return Map.from(value); } diff --git a/lib/collab/cloud_payload_upgrade.dart b/lib/collab/cloud_payload_upgrade.dart index fc230eb0..0ef97cdd 100644 --- a/lib/collab/cloud_payload_upgrade.dart +++ b/lib/collab/cloud_payload_upgrade.dart @@ -1,22 +1,26 @@ -import 'package:icarus/collab/cloud_lineup_rows.dart'; import 'package:icarus/collab/collab_models.dart'; import 'package:icarus/const/agents.dart'; import 'package:icarus/const/maps.dart'; import 'package:icarus/const/placed_classes.dart'; import 'package:icarus/migrations/paranoia_range_migration.dart'; -/// The payload version of a row holding a Paranoia, as an ability or a -/// lineup landing, drawn at its in-game size (data version 104). Every other -/// row stays at [currentCloudPayloadVersion]: nothing else changed shape. +/// The payload version of an ability row holding a Paranoia drawn at its +/// in-game size (data version 104). Every other row stays at +/// [currentCloudPayloadVersion]: nothing else changed shape. /// -/// A Paranoia row below this version was written by a client that drew the -/// old 25 m Paranoia. It is corrected as it is read, never rewritten on the -/// server, so every client reads it the same way until someone edits it. +/// A Paranoia ability row below this version was written by a client that +/// drew the old 25 m Paranoia. It is corrected as it is read, never +/// rewritten on the server, so every client reads it the same way until +/// someone edits it. +/// +/// Lineup rows are never corrected. The one-row lineup (protocol 5) came +/// after data version 104, so every lineup row holds its Paranoia at the +/// in-game size already; correcting one would move it twice. const paranoiaCloudPayloadVersion = 2; -/// [payload] with its Paranoia, if any, at the in-game size on [map]. -/// Payloads without a Paranoia, and those already corrected, come back as -/// they are. +/// Ability [payload] with its Paranoia, if any, at the in-game size on +/// [map]. Payloads without a Paranoia, those already corrected, and those of +/// any other kind come back as they are. CloudPayload upgradeCloudPayload(CloudPayload payload, MapValue map) { final version = payload['payloadVersion']; if (version is num && version >= paranoiaCloudPayloadVersion) { @@ -24,11 +28,8 @@ CloudPayload upgradeCloudPayload(CloudPayload payload, MapValue map) { } final data = payload['data']; if (data is! Map) return payload; - final upgraded = switch (payload['kind']) { - 'ability' => _upgradeAbilityJson(data, map), - CloudLineupKind.landing => _upgradeLandingJson(data, map), - _ => data, - }; + if (payload['kind'] != 'ability') return payload; + final upgraded = _upgradeAbilityJson(data, map); if (identical(upgraded, data)) return payload; return { ...payload, @@ -37,14 +38,9 @@ CloudPayload upgradeCloudPayload(CloudPayload payload, MapValue map) { }; } -/// The version a client writes [data] of [kind] at. +/// The version a client writes element [data] of [kind] at. int cloudPayloadVersionFor(String kind, Map data) { - final ability = switch (kind) { - 'ability' => data, - CloudLineupKind.landing => data['ability'], - _ => null, - }; - return ability is Map && _isParanoiaJson(ability) + return kind == 'ability' && _isParanoiaJson(data) ? paranoiaCloudPayloadVersion : currentCloudPayloadVersion; } @@ -60,7 +56,7 @@ RemotePageSnapshot upgradeRemotePageSnapshot( page: page.page, content: page.content, elements: [for (final e in page.elements) _upgradeElement(e, map)], - lineups: [for (final l in page.lineups) _upgradeLineup(l, map)], + lineups: page.lineups, assetsById: page.assetsById, ); } @@ -78,10 +74,7 @@ RemoteFullStrategySnapshot upgradeRemoteFullSnapshot( for (final MapEntry(:key, :value) in snapshot.elementsByPage.entries) key: [for (final e in value) _upgradeElement(e, map)], }, - lineupsByPage: { - for (final MapEntry(:key, :value) in snapshot.lineupsByPage.entries) - key: [for (final l in value) _upgradeLineup(l, map)], - }, + lineupsByPage: snapshot.lineupsByPage, assetsById: snapshot.assetsById, ); } @@ -101,20 +94,6 @@ RemoteElement _upgradeElement(RemoteElement element, MapValue map) { ); } -RemoteLineup _upgradeLineup(RemoteLineup lineup, MapValue map) { - final payload = upgradeCloudPayload(lineup.payload, map); - if (identical(payload, lineup.payload)) return lineup; - return RemoteLineup( - publicId: lineup.publicId, - strategyPublicId: lineup.strategyPublicId, - pagePublicId: lineup.pagePublicId, - payload: payload, - sortIndex: lineup.sortIndex, - revision: lineup.revision, - deleted: lineup.deleted, - ); -} - /// A map this build does not know cannot be drawn, so its rows stay as the /// server holds them. MapValue? _mapValueOrNull(String wireName) { @@ -131,16 +110,6 @@ bool _isParanoiaJson(Map ability) { info['index'] == 1; } -Map _upgradeLandingJson( - Map landing, - MapValue map, -) { - final ability = landing['ability']; - if (ability is! Map) return landing; - final moved = _upgradeAbilityJson(ability, map); - return identical(moved, ability) ? landing : {...landing, 'ability': moved}; -} - /// [ability] with only its position moved, or [ability] itself when it is /// not a Paranoia. A Paranoia this build cannot read is left as it is; /// hydration skips it the same way. diff --git a/lib/collab/cloud_sync_error_message.dart b/lib/collab/cloud_sync_error_message.dart index a8aab76f..e42ae0b4 100644 --- a/lib/collab/cloud_sync_error_message.dart +++ b/lib/collab/cloud_sync_error_message.dart @@ -32,8 +32,7 @@ bool isSpecificAttentionReason(String error) { final lower = error.toLowerCase(); return lower.contains('too large for cloud sync') || lower.contains(lineupPageMismatchMessage.toLowerCase()) || - lower.contains(lineupLinkEndMissingMessage.toLowerCase()) || - lower.contains(lineupEndInUseMessage.toLowerCase()) || + lower.contains(retiredLineupOpMessage.toLowerCase()) || lower.contains(pageDeletedMessage.toLowerCase()); } @@ -134,15 +133,10 @@ String friendlyCloudSyncError(String raw) { 'saved to the cloud. It remains on this device; Use cloud removes ' 'it here.'; } - if (lower.contains(lineupLinkEndMissingMessage.toLowerCase())) { - return "This lineup's origin or landing spot isn't on this page in the " - 'cloud, so your change to it was not saved. Keep mine tries again; ' - 'Use cloud drops your change.'; - } - if (lower.contains(lineupEndInUseMessage.toLowerCase())) { - return 'Another lineup in the cloud still uses this origin or landing ' - 'spot, so it was not deleted. Keep mine tries again; Use cloud ' - 'brings back that lineup here.'; + if (lower.contains(retiredLineupOpMessage.toLowerCase())) { + return 'A lineup change saved by an older version of Icarus cannot be ' + 'sent to the cloud, so it is not in this strategy. Redo it, then ' + 'choose Use cloud to clear this.'; } if (lower.contains(pageDeletedMessage.toLowerCase())) { return 'The page these changes were on was deleted, so they were not ' diff --git a/lib/collab/collab_models.dart b/lib/collab/collab_models.dart index ae7d2c2b..7d1dcfac 100644 --- a/lib/collab/collab_models.dart +++ b/lib/collab/collab_models.dart @@ -3,7 +3,7 @@ import 'dart:convert'; import 'package:icarus/collab/cloud_payload_upgrade.dart'; import 'package:icarus/const/sort_index_order.dart'; -const currentCloudProtocolVersion = 4; +const currentCloudProtocolVersion = 5; const currentCloudPayloadVersion = 1; const maxCloudOperationBytes = 900 * 1024; const maxCloudBatchBytes = 15 * 1024 * 1024; @@ -18,22 +18,18 @@ const cloudOperationTooLargeMessage = const cloudStrategyDeletedMessage = 'This strategy was deleted, so its unsent changes cannot be saved.'; -/// The server's message when it refuses a lineup link whose origin or -/// landing is not live on its page (LINEUP_LINK_END_MISSING). "Keep mine" -/// re-sends such a link as it was. -const lineupLinkEndMissingMessage = - "This lineup's origin or landing spot is no longer on the page"; - /// The server's message when it refuses a lineup whose row already lives on /// another page (LINEUP_PAGE_MISMATCH). const lineupPageMismatchMessage = 'This lineup belongs to another page and cannot be moved'; -/// The server's message when it refuses to delete an origin or landing that -/// a live link on its page still names (LINEUP_END_IN_USE), such as a -/// teammate's lineup this device has not drawn yet. -const lineupEndInUseMessage = - 'Another lineup still uses this origin or landing spot'; +/// Marks an outbox record holding a lineup change in the cloud format before +/// one row per lineup (an origin, landing or link row). No server takes it +/// any more, so it is never sent; it waits in attention until the user +/// discards it. +const retiredLineupOpMessage = + 'This lineup change was saved in an older cloud format and cannot be ' + 'sent'; /// The server's message when it refuses a change to a page in its trash /// (PAGE_DELETED). Such a change lands if the page is restored and it is @@ -61,17 +57,16 @@ CloudPayload cloudElementPayload({ }; } -/// One lineup row's payload; [kind] is a [CloudLineupKind] (see -/// cloud_lineup_rows.dart). -CloudPayload cloudLineupPayload({ - required String kind, - required Map data, -}) { - final normalized = _normalizeCloudPayloadData(data); +/// The payload kind of a lineup row (see cloud_lineup_rows.dart). +const cloudLineupPayloadKind = 'lineup'; + +/// One lineup row's payload. Lineup rows are never corrected on read (see +/// cloud_payload_upgrade.dart), so they all stay at the current version. +CloudPayload cloudLineupPayload(Map data) { return { - 'kind': kind, - 'payloadVersion': cloudPayloadVersionFor(kind, normalized), - 'data': normalized, + 'kind': cloudLineupPayloadKind, + 'payloadVersion': currentCloudPayloadVersion, + 'data': _normalizeCloudPayloadData(data), }; } diff --git a/lib/collab/convex_payload_codecs.dart b/lib/collab/convex_payload_codecs.dart index 4e2e07c2..ba9474b2 100644 --- a/lib/collab/convex_payload_codecs.dart +++ b/lib/collab/convex_payload_codecs.dart @@ -111,41 +111,13 @@ final class UtilityConvexCodec implements ConvexPayloadCodec { ConvexValue encode(CloudPayload value) => _encodePayload(value, 'utility'); } -@ConvexPayload('lineupOrigin') -final class LineupOriginConvexCodec - implements ConvexPayloadCodec { - const LineupOriginConvexCodec(); +@ConvexPayload('lineup') +final class LineupConvexCodec implements ConvexPayloadCodec { + const LineupConvexCodec(); @override - CloudPayload decode(ConvexValue value) => - _decodePayload(value, 'lineupOrigin'); + CloudPayload decode(ConvexValue value) => _decodePayload(value, 'lineup'); @override - ConvexValue encode(CloudPayload value) => - _encodePayload(value, 'lineupOrigin'); -} - -@ConvexPayload('lineupLanding') -final class LineupLandingConvexCodec - implements ConvexPayloadCodec { - const LineupLandingConvexCodec(); - - @override - CloudPayload decode(ConvexValue value) => - _decodePayload(value, 'lineupLanding'); - - @override - ConvexValue encode(CloudPayload value) => - _encodePayload(value, 'lineupLanding'); -} - -@ConvexPayload('lineupLink') -final class LineupLinkConvexCodec implements ConvexPayloadCodec { - const LineupLinkConvexCodec(); - - @override - CloudPayload decode(ConvexValue value) => _decodePayload(value, 'lineupLink'); - - @override - ConvexValue encode(CloudPayload value) => _encodePayload(value, 'lineupLink'); + ConvexValue encode(CloudPayload value) => _encodePayload(value, 'lineup'); } diff --git a/lib/collab/convex_strategy_repository.dart b/lib/collab/convex_strategy_repository.dart index d45886e1..89c6330b 100644 --- a/lib/collab/convex_strategy_repository.dart +++ b/lib/collab/convex_strategy_repository.dart @@ -349,12 +349,6 @@ class ConvexStrategyRepository { clientProtocolVersion: currentCloudProtocolVersion.toDouble(), ops: typedOps, accountSubject: _optional(accountSubject), - // Links are sent only with or after their origin and landing (see - // the outbox's batch claim), so the server may refuse an orphan. - checkLineupLinkEnds: const ConvexOptional.present(true), - // An origin or landing delete is sent only with or after the link - // deletes on its page, so the server may refuse one a live link names. - checkLineupEndDeletes: const ConvexOptional.present(true), // This client restores deleted pages, so it sends such a delete again. checkTrashedPageDeletes: const ConvexOptional.present(true), ); diff --git a/lib/collab/generated/convex_error_codes.dart b/lib/collab/generated/convex_error_codes.dart index cb5b8fe2..fb8b0bbb 100644 --- a/lib/collab/generated/convex_error_codes.dart +++ b/lib/collab/generated/convex_error_codes.dart @@ -22,8 +22,6 @@ enum ConvexErrorCode { invalidPayload('INVALID_PAYLOAD'), inviteExpired('INVITE_EXPIRED'), inviteRevoked('INVITE_REVOKED'), - lineupEndInUse('LINEUP_END_IN_USE'), - lineupLinkEndMissing('LINEUP_LINK_END_MISSING'), lineupPageMismatch('LINEUP_PAGE_MISMATCH'), lineupStrategyMismatch('LINEUP_STRATEGY_MISMATCH'), missingAddElementArgs('MISSING_ADD_ELEMENT_ARGS'), diff --git a/lib/collab/generated/convex_models.dart b/lib/collab/generated/convex_models.dart index 103f248e..cc10f41e 100644 --- a/lib/collab/generated/convex_models.dart +++ b/lib/collab/generated/convex_models.dart @@ -1285,8 +1285,10 @@ final class LineupsListForPageResultItem { object.value['pagePublicId'] ?? _missing(path, 'pagePublicId'), '$path.pagePublicId', ), - payload: _decodeLineupsListForPageResultItemPayload( - object.value['payload'] ?? _missing(path, 'payload'), + payload: _decodePayload( + () => const LineupConvexCodec().decode( + object.value['payload'] ?? _missing(path, 'payload'), + ), '$path.payload', ), publicId: _decodeString( @@ -1317,8 +1319,8 @@ final class LineupsListForPageResultItem { 'createdAt': _encodeNumber(createdAt, '$path.createdAt'), 'deleted': ConvexBoolean(deleted), 'pagePublicId': ConvexString(pagePublicId), - 'payload': _encodeLineupsListForPageResultItemPayload( - payload, + 'payload': _encodePayload( + () => const LineupConvexCodec().encode(payload), '$path.payload', ), 'publicId': ConvexString(publicId), @@ -1688,8 +1690,10 @@ final class OpsApplyBatchArgsOpsItemLineupAdd extends OpsApplyBatchArgsOpsItem { object.value['pagePublicId'] ?? _missing(path, 'pagePublicId'), '$path.pagePublicId', ), - payload: _decodeOpsApplyBatchArgsOpsItemLineupAddPayload( - object.value['payload'] ?? _missing(path, 'payload'), + payload: _decodePayload( + () => const LineupConvexCodec().decode( + object.value['payload'] ?? _missing(path, 'payload'), + ), '$path.payload', ), sortIndex: _decodeNumber( @@ -1711,8 +1715,8 @@ final class OpsApplyBatchArgsOpsItemLineupAdd extends OpsApplyBatchArgsOpsItem { 'lineupPublicId': ConvexString(lineupPublicId), 'opId': ConvexString(opId), 'pagePublicId': ConvexString(pagePublicId), - 'payload': _encodeOpsApplyBatchArgsOpsItemLineupAddPayload( - payload, + 'payload': _encodePayload( + () => const LineupConvexCodec().encode(payload), '$path.payload', ), 'sortIndex': _encodeNumber(sortIndex, '$path.sortIndex'), @@ -1836,8 +1840,9 @@ final class OpsApplyBatchArgsOpsItemLineupPatch : const ConvexOptional.absent(), payload: object.value.containsKey('payload') ? ConvexOptional.present( - _decodeOpsApplyBatchArgsOpsItemLineupPatchPayload( - object.value['payload']!, + _decodePayload( + () => + const LineupConvexCodec().decode(object.value['payload']!), '$path.payload', ), ) @@ -1863,8 +1868,8 @@ final class OpsApplyBatchArgsOpsItemLineupPatch if (pagePublicId.isPresent) 'pagePublicId': ConvexString(pagePublicId.value), if (payload.isPresent) - 'payload': _encodeOpsApplyBatchArgsOpsItemLineupPatchPayload( - payload.value, + 'payload': _encodePayload( + () => const LineupConvexCodec().encode(payload.value), '$path.payload', ), if (sortIndex.isPresent) @@ -2857,8 +2862,10 @@ final class OpsApplyBatchResultResultsItemRejectedCurrentLineup object.value['revision'] ?? _missing(path, 'revision'), '$path.revision', ), - value: _decodeOpsApplyBatchResultResultsItemRejectedCurrentLineupValue( - object.value['value'] ?? _missing(path, 'value'), + value: _decodePayload( + () => const LineupConvexCodec().decode( + object.value['value'] ?? _missing(path, 'value'), + ), '$path.value', ), ); @@ -2869,8 +2876,8 @@ final class OpsApplyBatchResultResultsItemRejectedCurrentLineup return ConvexObject({ 'type': ConvexString('lineup'), 'revision': _encodeNumber(revision, '$path.revision'), - 'value': _encodeOpsApplyBatchResultResultsItemRejectedCurrentLineupValue( - value, + 'value': _encodePayload( + () => const LineupConvexCodec().encode(value), '$path.value', ), }); @@ -4457,60 +4464,6 @@ ConvexValue _encodeElementsListForPageResultItemPayload( }; } -CloudPayload _decodeLineupsListForPageResultItemPayload( - ConvexValue value, - String path, -) { - final object = _decodeObject(value, path); - final tag = _decodeString( - object.value['kind'] ?? _missing(path, 'kind'), - '$path.kind', - ); - return switch (tag) { - 'lineupOrigin' => _decodePayload( - () => const LineupOriginConvexCodec().decode(value), - path, - ), - 'lineupLanding' => _decodePayload( - () => const LineupLandingConvexCodec().decode(value), - path, - ), - 'lineupLink' => _decodePayload( - () => const LineupLinkConvexCodec().decode(value), - path, - ), - _ => throw ConvexDecodingException( - '$path.kind', - 'unknown payload tag $tag', - ), - }; -} - -ConvexValue _encodeLineupsListForPageResultItemPayload( - CloudPayload value, - String path, -) { - final tag = value['kind']; - return switch (tag) { - 'lineupOrigin' => _encodePayload( - () => const LineupOriginConvexCodec().encode(value), - path, - ), - 'lineupLanding' => _encodePayload( - () => const LineupLandingConvexCodec().encode(value), - path, - ), - 'lineupLink' => _encodePayload( - () => const LineupLinkConvexCodec().encode(value), - path, - ), - _ => throw ConvexEncodingException( - '$path.kind', - 'unknown payload tag $tag', - ), - }; -} - CloudPayload _decodeOpsApplyBatchArgsOpsItemElementAddPayload( ConvexValue value, String path, @@ -4655,114 +4608,6 @@ ConvexValue _encodeOpsApplyBatchArgsOpsItemElementPatchPayload( }; } -CloudPayload _decodeOpsApplyBatchArgsOpsItemLineupAddPayload( - ConvexValue value, - String path, -) { - final object = _decodeObject(value, path); - final tag = _decodeString( - object.value['kind'] ?? _missing(path, 'kind'), - '$path.kind', - ); - return switch (tag) { - 'lineupOrigin' => _decodePayload( - () => const LineupOriginConvexCodec().decode(value), - path, - ), - 'lineupLanding' => _decodePayload( - () => const LineupLandingConvexCodec().decode(value), - path, - ), - 'lineupLink' => _decodePayload( - () => const LineupLinkConvexCodec().decode(value), - path, - ), - _ => throw ConvexDecodingException( - '$path.kind', - 'unknown payload tag $tag', - ), - }; -} - -ConvexValue _encodeOpsApplyBatchArgsOpsItemLineupAddPayload( - CloudPayload value, - String path, -) { - final tag = value['kind']; - return switch (tag) { - 'lineupOrigin' => _encodePayload( - () => const LineupOriginConvexCodec().encode(value), - path, - ), - 'lineupLanding' => _encodePayload( - () => const LineupLandingConvexCodec().encode(value), - path, - ), - 'lineupLink' => _encodePayload( - () => const LineupLinkConvexCodec().encode(value), - path, - ), - _ => throw ConvexEncodingException( - '$path.kind', - 'unknown payload tag $tag', - ), - }; -} - -CloudPayload _decodeOpsApplyBatchArgsOpsItemLineupPatchPayload( - ConvexValue value, - String path, -) { - final object = _decodeObject(value, path); - final tag = _decodeString( - object.value['kind'] ?? _missing(path, 'kind'), - '$path.kind', - ); - return switch (tag) { - 'lineupOrigin' => _decodePayload( - () => const LineupOriginConvexCodec().decode(value), - path, - ), - 'lineupLanding' => _decodePayload( - () => const LineupLandingConvexCodec().decode(value), - path, - ), - 'lineupLink' => _decodePayload( - () => const LineupLinkConvexCodec().decode(value), - path, - ), - _ => throw ConvexDecodingException( - '$path.kind', - 'unknown payload tag $tag', - ), - }; -} - -ConvexValue _encodeOpsApplyBatchArgsOpsItemLineupPatchPayload( - CloudPayload value, - String path, -) { - final tag = value['kind']; - return switch (tag) { - 'lineupOrigin' => _encodePayload( - () => const LineupOriginConvexCodec().encode(value), - path, - ), - 'lineupLanding' => _encodePayload( - () => const LineupLandingConvexCodec().encode(value), - path, - ), - 'lineupLink' => _encodePayload( - () => const LineupLinkConvexCodec().encode(value), - path, - ), - _ => throw ConvexEncodingException( - '$path.kind', - 'unknown payload tag $tag', - ), - }; -} - CloudPayload _decodeOpsApplyBatchResultResultsItemRejectedCurrentElementValue( ConvexValue value, String path, @@ -4835,60 +4680,6 @@ ConvexValue _encodeOpsApplyBatchResultResultsItemRejectedCurrentElementValue( }; } -CloudPayload _decodeOpsApplyBatchResultResultsItemRejectedCurrentLineupValue( - ConvexValue value, - String path, -) { - final object = _decodeObject(value, path); - final tag = _decodeString( - object.value['kind'] ?? _missing(path, 'kind'), - '$path.kind', - ); - return switch (tag) { - 'lineupOrigin' => _decodePayload( - () => const LineupOriginConvexCodec().decode(value), - path, - ), - 'lineupLanding' => _decodePayload( - () => const LineupLandingConvexCodec().decode(value), - path, - ), - 'lineupLink' => _decodePayload( - () => const LineupLinkConvexCodec().decode(value), - path, - ), - _ => throw ConvexDecodingException( - '$path.kind', - 'unknown payload tag $tag', - ), - }; -} - -ConvexValue _encodeOpsApplyBatchResultResultsItemRejectedCurrentLineupValue( - CloudPayload value, - String path, -) { - final tag = value['kind']; - return switch (tag) { - 'lineupOrigin' => _encodePayload( - () => const LineupOriginConvexCodec().encode(value), - path, - ), - 'lineupLanding' => _encodePayload( - () => const LineupLandingConvexCodec().encode(value), - path, - ), - 'lineupLink' => _encodePayload( - () => const LineupLinkConvexCodec().encode(value), - path, - ), - _ => throw ConvexEncodingException( - '$path.kind', - 'unknown payload tag $tag', - ), - }; -} - bool _validateFoldersCreateResult(ConvexValue value) => (_matchesRaw0(value) || _matchesRaw2(value)); @@ -5479,8 +5270,6 @@ List decodeLineupsListForStrategyResult( ConvexObject encodeOpsApplyBatchArgs({ ConvexOptional accountSubject = const ConvexOptional.absent(), - ConvexOptional checkLineupEndDeletes = const ConvexOptional.absent(), - ConvexOptional checkLineupLinkEnds = const ConvexOptional.absent(), ConvexOptional checkTrashedPageDeletes = const ConvexOptional.absent(), required String clientId, required double clientProtocolVersion, @@ -5489,10 +5278,6 @@ ConvexObject encodeOpsApplyBatchArgs({ }) => ConvexObject({ if (accountSubject.isPresent) 'accountSubject': ConvexString(accountSubject.value), - if (checkLineupEndDeletes.isPresent) - 'checkLineupEndDeletes': ConvexBoolean(checkLineupEndDeletes.value), - if (checkLineupLinkEnds.isPresent) - 'checkLineupLinkEnds': ConvexBoolean(checkLineupLinkEnds.value), if (checkTrashedPageDeletes.isPresent) 'checkTrashedPageDeletes': ConvexBoolean(checkTrashedPageDeletes.value), 'clientId': ConvexString(clientId), diff --git a/lib/collab/generated/icarus_convex_api.dart b/lib/collab/generated/icarus_convex_api.dart index a4facdd0..1fe40a4c 100644 --- a/lib/collab/generated/icarus_convex_api.dart +++ b/lib/collab/generated/icarus_convex_api.dart @@ -726,8 +726,6 @@ final class _LineupsModule implements LineupsModule { abstract interface class OpsModule { Future applyBatch({ ConvexOptional accountSubject = const ConvexOptional.absent(), - ConvexOptional checkLineupEndDeletes = const ConvexOptional.absent(), - ConvexOptional checkLineupLinkEnds = const ConvexOptional.absent(), ConvexOptional checkTrashedPageDeletes = const ConvexOptional.absent(), required String clientId, @@ -743,8 +741,6 @@ final class _OpsModule implements OpsModule { @override Future applyBatch({ ConvexOptional accountSubject = const ConvexOptional.absent(), - ConvexOptional checkLineupEndDeletes = const ConvexOptional.absent(), - ConvexOptional checkLineupLinkEnds = const ConvexOptional.absent(), ConvexOptional checkTrashedPageDeletes = const ConvexOptional.absent(), required String clientId, @@ -754,8 +750,6 @@ final class _OpsModule implements OpsModule { }) { final args = encodeOpsApplyBatchArgs( accountSubject: accountSubject, - checkLineupEndDeletes: checkLineupEndDeletes, - checkLineupLinkEnds: checkLineupLinkEnds, checkTrashedPageDeletes: checkTrashedPageDeletes, clientId: clientId, clientProtocolVersion: clientProtocolVersion, diff --git a/lib/const/line_provider.dart b/lib/const/line_provider.dart index af06eae2..dd8b1592 100644 --- a/lib/const/line_provider.dart +++ b/lib/const/line_provider.dart @@ -1438,6 +1438,16 @@ class LineUpProvider extends Notifier { ); } + /// Takes [graph] as the server's lineups merged with the ones the user + /// holds (see mergeHeldLineups). Held objects stay the ones on screen. + void mergeRemote(LineUpGraph graph) { + state = state.copyWith( + origins: [...graph.origins], + landings: [...graph.landings], + links: [...graph.links], + ); + } + static String objectToJson(LineUpGraph graph) { return jsonEncode(graph.toJson()); } diff --git a/lib/providers/collab/active_page_live_sync_models.dart b/lib/providers/collab/active_page_live_sync_models.dart index bed2115d..ca7ab8de 100644 --- a/lib/providers/collab/active_page_live_sync_models.dart +++ b/lib/providers/collab/active_page_live_sync_models.dart @@ -190,11 +190,15 @@ class ProjectedPageLineup { required this.publicId, required this.payload, required this.sortIndex, + required this.revision, }); final String publicId; final CloudPayload payload; final int sortIndex; + + /// Ranks this lineup's copies of shared spots (see CloudLineupRow). + final int revision; } class ActivePageProjectedState { diff --git a/lib/providers/collab/active_page_live_sync_provider.dart b/lib/providers/collab/active_page_live_sync_provider.dart index 921fc1dc..d3a17092 100644 --- a/lib/providers/collab/active_page_live_sync_provider.dart +++ b/lib/providers/collab/active_page_live_sync_provider.dart @@ -5,7 +5,6 @@ import 'dart:math' show max; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:icarus/collab/canonical_json.dart'; import 'package:icarus/const/sort_index_order.dart'; -import 'package:icarus/services/app_error_reporter.dart'; import 'package:icarus/const/weapons.dart'; import 'package:icarus/collab/collab_models.dart'; import 'package:icarus/collab/cloud_lineup_rows.dart'; @@ -33,7 +32,6 @@ class ActivePageLiveSyncState { this.remoteBaseRevisionByEntity = const {}, this.overlayByEntityKey = const {}, this.lastAckBatch = const [], - this.unsyncableLineupKeys = const {}, }); final String? strategyPublicId; @@ -44,14 +42,6 @@ class ActivePageLiveSyncState { final Map overlayByEntityKey; final List lastAckBatch; - /// Lineup rows live sync refused to send because the canvas holds a broken - /// lineup: rows hydration would not draw back (a link whose origin or - /// landing is missing, an origin or landing no link uses) and the missing - /// ends themselves. Sending them would store rows every reader skips or - /// half-delete the lineup, so the sync status shows attention while any - /// remain. - final Set unsyncableLineupKeys; - ActivePageLiveSyncState copyWith({ String? strategyPublicId, String? activePageId, @@ -62,7 +52,6 @@ class ActivePageLiveSyncState { Map? remoteBaseRevisionByEntity, Map? overlayByEntityKey, List? lastAckBatch, - Set? unsyncableLineupKeys, }) { return ActivePageLiveSyncState( strategyPublicId: strategyPublicId ?? this.strategyPublicId, @@ -75,7 +64,6 @@ class ActivePageLiveSyncState { remoteBaseRevisionByEntity ?? this.remoteBaseRevisionByEntity, overlayByEntityKey: overlayByEntityKey ?? this.overlayByEntityKey, lastAckBatch: lastAckBatch ?? this.lastAckBatch, - unsyncableLineupKeys: unsyncableLineupKeys ?? this.unsyncableLineupKeys, ); } } @@ -173,15 +161,8 @@ class ActivePageLiveSyncNotifier extends Notifier { setContext(strategyPublicId: strategyPublicId, activePageId: pageId); final matchesPage = snapshot.header.publicId == strategyPublicId && snapshot.activePage?.page.publicId == pageId; - // The base is what the canvas drew. A lineup row hydration skipped was - // never shown, so no outbound diff may change or delete it. - final undrawnLineups = matchesPage - ? _undrawnRemoteLineups(snapshot, pageId) - : const {}; final remoteEntities = { - if (matchesPage) - for (final entry in _normalizedRemoteEntities(snapshot, pageId).entries) - if (!undrawnLineups.contains(entry.key)) entry.key: entry.value, + if (matchesPage) ..._normalizedRemoteEntities(snapshot, pageId), }; _heldDeletedKeys ..removeWhere((key) => key.pageId == pageId) @@ -248,7 +229,6 @@ class ActivePageLiveSyncNotifier extends Notifier { RemoteEditorSnapshot snapshot, String pageId, ) { - final undrawn = _undrawnRemoteLineups(snapshot, pageId); _NormalizedEntity? live(_NormalizedEntity? entity) => entity == null || entity.deleted ? null : entity; final remote = _normalizedRemoteEntities(snapshot, pageId); @@ -258,10 +238,10 @@ class ActivePageLiveSyncNotifier extends Notifier { }; return { for (final key in keys) - // A row the canvas cannot draw counts as gone on the server side - // only: the base is what the canvas did draw. - if (undrawn.contains(key) ? null : live(remote[key]) case final now - when !_sameLiveEntity(now, live(_hydratedBaseByEntityKey[key]))) + if (!_sameLiveEntity( + live(remote[key]), + live(_hydratedBaseByEntityKey[key]), + )) key, }; } @@ -536,13 +516,6 @@ class ActivePageLiveSyncNotifier extends Notifier { state.overlayByEntityKey, ); final retainedDesiredOps = {}; - final heldBackLineups = _heldBackLineups(pageId); - final restoredLineupEnds = _lineupEndsToRestore( - pageId: pageId, - localEntities: localEntities, - remoteEntities: remoteEntities, - ); - final unsyncableLineups = {}; for (final key in pageKeys) { if (_remoteAdoptionPending.contains(key)) { @@ -561,41 +534,6 @@ class ActivePageLiveSyncNotifier extends Notifier { queueState.inFlightByEntityKey[key]?.pending.op ?? queueState.queuedByEntityKey[key]?.pending.op; - // Never author a lineup row that hydration would not draw (every - // reader skips it, so it reads as a deletion nobody made), nor delete - // an end a local link still names. Keep what is already overlaid or - // queued for it and show attention instead. - if (heldBackLineups.contains(key)) { - unsyncableLineups.add(key); - if (!state.unsyncableLineupKeys.contains(key)) { - AppErrorReporter.reportError( - 'A lineup could not be synced because its origin or landing ' - 'spot is missing ($key).', - source: 'active_page_live_sync:empty_lineup', - promptUser: false, - ); - } - if (existingOverlay == null && retainedOp != null) { - retainedDesiredOps[key] = retainedOp; - } - continue; - } - - if (restoredLineupEnds.contains(key)) { - nextOverlay[key] = ActivePageOverlayEntry( - entityKey: key, - entityType: ActivePageOverlayEntityType.lineup, - desiredPayload: local!.payload, - desiredSortIndex: local.sortIndex, - deletion: false, - baseRevision: remote?.revision, - baseDeleted: true, - dirtyAt: DateTime.now(), - ); - _debugLog('overlay.upsert $key reason=restore_deleted_lineup_end'); - continue; - } - final shouldPreserveTouched = hasQueued || hasInFlight || hasSuccessor; final matchesRemote = _entitiesEquivalent(local, remote); final matchesHydratedBase = _entitiesEquivalent(local, hydratedBase); @@ -725,10 +663,6 @@ class ActivePageLiveSyncNotifier extends Notifier { strategyPublicId: strategyPublicId, activePageId: pageId, overlayByEntityKey: nextOverlay, - unsyncableLineupKeys: { - ...state.unsyncableLineupKeys.where((key) => key.pageId != pageId), - ...unsyncableLineups, - }, ); return desiredOpsByEntityKey; @@ -773,6 +707,7 @@ class ActivePageLiveSyncNotifier extends Notifier { publicId: lineup.publicId, payload: lineup.payload, sortIndex: lineup.sortIndex, + revision: lineup.revision, ), }; @@ -842,6 +777,9 @@ class ActivePageLiveSyncNotifier extends Notifier { publicId: lineupId, payload: Map.from(overlay.desiredPayload! as Map), sortIndex: overlay.desiredSortIndex ?? 0, + // The user's own change to a shared spot is the copy drawn + // until it lands. + revision: pendingCloudLineupRevision, ); continue; } @@ -922,13 +860,21 @@ class ActivePageLiveSyncNotifier extends Notifier { ); } - for (final lineup - in (snapshot.lineupsByPage[page.publicId] ?? const [])) { + // A live lineup compares as the canvas draws it: carrying the drawn copy + // of each spot it shares, so the copy hydration chose never reads as an + // edit to the rows whose copy lost. + final lineups = + snapshot.lineupsByPage[page.publicId] ?? const []; + final drawnLineups = drawnCloudLineupPayloads([ + for (final lineup in lineups) + if (!lineup.deleted) CloudLineupRow.remote(lineup), + ]); + for (final lineup in lineups) { final key = EntitySyncKey.lineup(page.publicId, lineup.publicId); entities[key] = _NormalizedEntity( key: key, overlayEntityType: ActivePageOverlayEntityType.lineup, - payload: lineup.payload, + payload: drawnLineups[lineup.publicId] ?? lineup.payload, sortIndex: lineup.sortIndex, revision: lineup.revision, deleted: lineup.deleted, @@ -1027,7 +973,8 @@ class ActivePageLiveSyncNotifier extends Notifier { ); } - // One row per origin, landing and link. + // One row per lineup, carrying its origin and landing as they are now, so + // a change to a shared spot changes every lineup that uses it. final freshLineupSortIndex = freshSortIndexes(EntitySyncKeyKind.lineup); for (final row in cloudLineupRows(ref.read(lineUpProvider).graph)) { final key = EntitySyncKey.lineup(pageId, row.publicId); @@ -1044,89 +991,6 @@ class ActivePageLiveSyncNotifier extends Notifier { return entities; } - /// Lineup rows live sync holds back from a broken local graph: rows the - /// canvas holds that hydration would not draw back (a link whose origin or - /// landing is missing, an origin or landing no link uses), judged by the - /// reader hydration uses, and the missing ends such a link still names, so - /// a broken lineup is neither half-written nor half-deleted. - Set _heldBackLineups(String pageId) { - EntitySyncKey key(String rowId) => EntitySyncKey.lineup(pageId, rowId); - final graph = ref.read(lineUpProvider).graph; - final rows = cloudLineupRows(graph); - final drawn = lineUpGraphFromCloudRows(rows).drawnRowIds; - final originIds = {for (final origin in graph.origins) origin.id}; - final landingIds = {for (final landing in graph.landings) landing.id}; - return { - for (final row in rows) - if (!drawn.contains(row.publicId)) key(row.publicId), - for (final link in graph.links) ...[ - if (!originIds.contains(link.originId)) - key(cloudLineupRowId(CloudLineupKind.origin, link.originId)), - if (!landingIds.contains(link.landingId)) - key(cloudLineupRowId(CloudLineupKind.landing, link.landingId)), - ], - }; - } - - /// Origins and landings a teammate deleted after this canvas drew them, - /// which a link the user is placing or editing still names. They are added - /// back with the link: the server refuses a link whose ends are gone, and - /// the lineup the user just made would not save. A link the teammate - /// deleted too is left to the conflict flow. - Set _lineupEndsToRestore({ - required String pageId, - required Map localEntities, - required Map remoteEntities, - }) { - EntitySyncKey key(String kind, String id) => - EntitySyncKey.lineup(pageId, cloudLineupRowId(kind, id)); - bool deletedSinceDrawn(EntitySyncKey endKey) { - final drawn = _hydratedBaseByEntityKey[endKey]; - final remote = remoteEntities[endKey]; - return localEntities.containsKey(endKey) && - drawn != null && - !drawn.deleted && - (remote == null || remote.deleted); - } - - final ends = {}; - for (final link in ref.read(lineUpProvider).graph.links) { - final linkKey = key(CloudLineupKind.link, link.id); - final isAuthored = !_entitiesEquivalent( - localEntities[linkKey], - _hydratedBaseByEntityKey[linkKey], - ); - final isDeletedRemotely = remoteEntities[linkKey]?.deleted ?? false; - if (!isAuthored || isDeletedRemotely) continue; - for (final endKey in [ - key(CloudLineupKind.origin, link.originId), - key(CloudLineupKind.landing, link.landingId), - ]) { - if (deletedSinceDrawn(endKey)) ends.add(endKey); - } - } - return ends; - } - - /// The page's live lineup rows that hydration skipped. - Set _undrawnRemoteLineups( - RemoteEditorSnapshot snapshot, - String pageId, - ) { - final live = [ - for (final lineup - in snapshot.lineupsByPage[pageId] ?? const []) - if (!lineup.deleted) - CloudLineupRow(publicId: lineup.publicId, payload: lineup.payload), - ]; - final drawn = lineUpGraphFromCloudRows(live).drawnRowIds; - return { - for (final row in live) - if (!drawn.contains(row.publicId)) - EntitySyncKey.lineup(pageId, row.publicId), - }; - } - List<_CollabElementEnvelope> _collectLocalElementEnvelopes() { final envelopes = <_CollabElementEnvelope>[]; diff --git a/lib/providers/collab/cloud_sync_status_provider.dart b/lib/providers/collab/cloud_sync_status_provider.dart index 23acc5e0..082b5d26 100644 --- a/lib/providers/collab/cloud_sync_status_provider.dart +++ b/lib/providers/collab/cloud_sync_status_provider.dart @@ -1,5 +1,4 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:icarus/providers/collab/active_page_live_sync_provider.dart'; import 'package:icarus/providers/collab/client_upgrade_required_provider.dart'; import 'package:icarus/providers/collab/cloud_media_upload_queue_provider.dart'; import 'package:icarus/providers/collab/convex_connection_provider.dart'; @@ -49,9 +48,6 @@ final cloudSyncStatusProvider = Provider((ref) { } if (opQueueState.needsAttention || opQueueState.accountOutbox.needsAttention || - ref.watch(activePageLiveSyncProvider.select( - (liveSync) => liveSync.unsyncableLineupKeys.isNotEmpty, - )) || saveState.mediaSyncErrorCount > 0 || accountMediaErrorCount > 0) { return CloudSyncStatus.attention; diff --git a/lib/providers/collab/strategy_op_queue_provider.dart b/lib/providers/collab/strategy_op_queue_provider.dart index 39e85003..7487e479 100644 --- a/lib/providers/collab/strategy_op_queue_provider.dart +++ b/lib/providers/collab/strategy_op_queue_provider.dart @@ -975,17 +975,16 @@ class StrategyOpQueueNotifier extends Notifier { final rejectedOp = rejected.op; final successor = record?.successorPending; final retryOp = successor?.op ?? rejectedOp; + // No server takes a lineup change in the old cloud format; sending + // it again would only bring it back here. + if (isRetiredCloudLineupOp(retryOp)) continue; final isPayloadPolicyAttention = record?.lastError == cloudOperationTooLargeMessage || cloudOperationExceedsPolicy(rejectedOp); - // Nothing moved on the server: the link is re-sent as it was, - // add or patch, once its ends are back; a change to a page in the - // trash, once the page is restored. - final isMissingLinkEnd = - record?.lastError == lineupLinkEndMissingMessage; + // Nothing moved on the server: a change to a page in the trash is + // re-sent as it was once the page is restored. final isOnDeletedPage = record?.lastError == pageDeletedMessage; - final retriesAsSent = - isPayloadPolicyAttention || isMissingLinkEnd || isOnDeletedPage; + final retriesAsSent = isPayloadPolicyAttention || isOnDeletedPage; final retryRevision = record?.latestServerRevision ?? rejectedOp.expectedRevision; if (!retriesAsSent && retryRevision == null) continue; @@ -1042,9 +1041,21 @@ class StrategyOpQueueNotifier extends Notifier { } if (!changed) { if (only != null) return; + // Old-format lineup changes are skipped on purpose, and keep their + // own reason. + final onlyRetired = attention.values.every( + (intent) => isRetiredCloudLineupOp(intent.pending.op), + ); state = state.copyWith( - lastError: 'Some retained cloud work cannot be retried ' - 'automatically because the server has no matching revision.', + lastError: onlyRetired + ? _loadedAttentionMessage( + loadIssues: state.loadIssues, + paused: state.pausedByEntityKey, + attention: attention, + ) + : 'Some retained cloud work cannot be retried ' + 'automatically because the server has no matching ' + 'revision.', ); return; } @@ -1365,15 +1376,35 @@ class StrategyOpQueueNotifier extends Notifier { _retryTimer?.cancel(); _retryTimer = null; _offlineRetryCount = 0; - final acks = await _repo.applyBatch( - strategyPublicId: strategyPublicId, - clientId: batchClientId, - ops: batch.map((record) => record.pending.op).toList(growable: false), - // The transport can deliver this after a later sign-in (it resends - // pending requests on reconnect), so the server checks the batch's - // own account, not whoever is signed in then. - accountSubject: batch.first.accountId, - ); + // A lineup change in the old cloud format has no shape the server + // takes, so it is refused here, as the server would refuse it, and + // waits in attention with the rest of the batch sent. + final sendable = [ + for (final record in batch) + if (!isRetiredCloudLineupOp(record.pending.op)) record, + ]; + final acks = [ + if (sendable.isNotEmpty) + ...await _repo.applyBatch( + strategyPublicId: strategyPublicId, + clientId: batchClientId, + ops: sendable + .map((record) => record.pending.op) + .toList(growable: false), + // The transport can deliver this after a later sign-in (it + // resends pending requests on reconnect), so the server checks + // the batch's own account, not whoever is signed in then. + accountSubject: batch.first.accountId, + ), + for (final record in batch) + if (isRetiredCloudLineupOp(record.pending.op)) + FailedOpAck( + opId: record.pending.op.opId, + code: 'INVALID_LINEUP_PAYLOAD_KIND', + rawCode: 'INVALID_LINEUP_PAYLOAD_KIND', + message: retiredLineupOpMessage, + ), + ]; await _applyAcksForRecords(batch, acks); batchSucceeded = true; } catch (error, stackTrace) { @@ -1425,75 +1456,6 @@ class StrategyOpQueueNotifier extends Notifier { } } - /// The origin and landing rows a lineup link add or patch names; empty - /// for any other op. - static Set _lineupLinkEndKeys(StrategyOp op) { - final pageId = op.pagePublicId; - final payload = op.payload; - final data = payload is Map ? payload['data'] : null; - if (op.entityType != StrategyOpEntityType.lineup || - payload is! Map || - payload['kind'] != CloudLineupKind.link || - pageId == null || - data is! Map) { - return const {}; - } - return { - EntitySyncKey.lineup( - pageId, - cloudLineupRowId(CloudLineupKind.origin, '${data['originId']}'), - ), - EntitySyncKey.lineup( - pageId, - cloudLineupRowId(CloudLineupKind.landing, '${data['landingId']}'), - ), - }; - } - - /// Whether [op] deletes a lineup row of one of [kinds]. - static bool _deletesLineupRow(StrategyOp op, List kinds) => - op is LineupDeleteOp && - kinds.any((kind) => op.lineupPublicId.startsWith('$kind:')); - - /// Whether [record] is a lineup op that must wait for another one still - /// queued or in flight (queued behind it, past a batch cap, under another - /// client id, backing off) outside [sentWith], the batch being claimed. - /// The server refuses a link whose origin or landing is not live, so a - /// link add or patch waits for the records of the ends it names. It also - /// refuses deleting an origin or landing a live link names, so an end - /// delete waits for every link delete on its page. Either is due once the - /// record it waits for has been sent. - bool _waitsForLineupOrder( - DurableOutboxRecord record, { - Set sentWith = const {}, - }) { - bool isUnsent(DurableOutboxRecord other) => - !sentWith.contains(other.entityKey) && - (other.status == DurableOutboxStatus.queued || - other.status == DurableOutboxStatus.inFlight); - - final op = record.pending.op; - if (_deletesLineupRow( - op, - const [CloudLineupKind.origin, CloudLineupKind.landing], - )) { - return _recordsByStorageKey.values.any((other) => - other.accountId == record.accountId && - other.strategyPublicId == record.strategyPublicId && - other.entityKey.pageId == record.entityKey.pageId && - _deletesLineupRow(other.pending.op, const [CloudLineupKind.link]) && - isUnsent(other)); - } - return _lineupLinkEndKeys(op).any((endKey) { - final end = _recordsByStorageKey[DurableOutboxRecord.createStorageKey( - accountId: record.accountId, - strategyPublicId: record.strategyPublicId, - entityKey: endKey, - )]; - return end != null && isUnsent(end); - }); - } - Future> _claimBatch({ required String accountId, required String strategyPublicId, @@ -1512,26 +1474,10 @@ class StrategyOpQueueNotifier extends Notifier { (ignoreBackoff || !_nextAttemptAt(record).isAfter(now))) .toList(growable: false); if (candidates.isEmpty) return const []; - // Link deletes go first, so the origin and landing deletes of a whole - // lineup can follow them in the same batch. - bool deletesLink(DurableOutboxRecord record) => _deletesLineupRow( - record.pending.op, - const [CloudLineupKind.link], - ); - final ordered = [ - ...candidates.where(deletesLink), - ...candidates.where((record) => !deletesLink(record)), - ]; final selected = []; - final selectedKeys = {}; - bool mustWait(DurableOutboxRecord record) => - _waitsForLineupOrder(record, sentWith: selectedKeys); - final sendable = ordered.where((record) => !mustWait(record)); - if (sendable.isEmpty) return const []; - final batchClientId = sendable.first.pending.clientId; - for (final candidate in ordered) { + final batchClientId = candidates.first.pending.clientId; + for (final candidate in candidates) { if (candidate.pending.clientId != batchClientId) continue; - if (mustWait(candidate)) continue; if (selected.length >= _maxBatchSize) break; final nextSelection = [...selected, candidate]; final byteSize = serializedCloudBatchUtf8Bytes( @@ -1541,7 +1487,6 @@ class StrategyOpQueueNotifier extends Notifier { ); if (byteSize > maxCloudBatchBytes) break; selected.add(candidate); - selectedKeys.add(candidate.entityKey); } final claimed = []; for (final record in selected) { @@ -2424,8 +2369,7 @@ class StrategyOpQueueNotifier extends Notifier { (record.status == DurableOutboxStatus.queued || record.status == DurableOutboxStatus.inFlight) && !_uncertainDurableRecords.contains(record.storageKey) && - !cloudOperationExceedsPolicy(record.pending.op) && - !_waitsForLineupOrder(record)) + !cloudOperationExceedsPolicy(record.pending.op)) .toList(growable: false); if (candidates.isEmpty) return; final nextAttempt = candidates @@ -2469,7 +2413,6 @@ class StrategyOpQueueNotifier extends Notifier { record.status == DurableOutboxStatus.inFlight) && !_uncertainDurableRecords.contains(record.storageKey) && !cloudOperationExceedsPolicy(record.pending.op) && - !_waitsForLineupOrder(record) && (ignoreBackoff || !_nextAttemptAt(record).isAfter(now))) .toList(growable: false) ..sort((left, right) => left.updatedAt.compareTo(right.updatedAt)); @@ -2840,12 +2783,15 @@ class StrategyOpQueueNotifier extends Notifier { var hasOtherWork = false; for (final entry in attention.entries) { final record = recordFor(entry.key); - final reason = record?.pending.op.opId == entry.value.pending.op.opId - ? record?.lastError - : null; - if (reason == lineupLinkEndMissingMessage || - reason == lineupPageMismatchMessage || - reason == lineupEndInUseMessage || + // An old-format lineup change keeps its reason whatever an older + // build recorded for it. + final reason = isRetiredCloudLineupOp(entry.value.pending.op) + ? retiredLineupOpMessage + : record?.pending.op.opId == entry.value.pending.op.opId + ? record?.lastError + : null; + if (reason == lineupPageMismatchMessage || + reason == retiredLineupOpMessage || reason == pageDeletedMessage) { specificReason ??= reason; } else { diff --git a/lib/strategy/remote_page_merge.dart b/lib/strategy/remote_page_merge.dart index 9bd0195a..bbd82c44 100644 --- a/lib/strategy/remote_page_merge.dart +++ b/lib/strategy/remote_page_merge.dart @@ -1,5 +1,7 @@ import 'dart:math' show min; +import 'package:icarus/const/line_provider.dart'; + /// The server's copy of a page's items ([incoming]), except the ones [keep] /// names: those stay the object on screen, so the widget keeps whatever /// in-progress state it holds, and stay even when the server removed them. @@ -29,3 +31,79 @@ List mergeRemoteItems({ } return merged; } + +/// The server's lineups ([remote]), except the ones the user is holding: +/// those stay as the canvas ([local]) has them, as [mergeRemoteItems] keeps +/// held items. Returns the merged graph and the ids of the held lineups. +/// +/// A lineup is held when the user holds it, its origin or its landing, or +/// when it shares a spot with a held lineup: a shared spot is drawn once, +/// so every lineup on it takes the same side. The other lineups take the +/// server's copy. A held lineup only the server has (a teammate's new +/// lineup on a held spot) waits off the canvas until the hold ends, like a +/// held lineup the server removed waits on it. +(LineUpGraph, Set) mergeHeldLineups({ + required LineUpGraph local, + required LineUpGraph remote, + required Set holding, +}) { + final allLinks = [...local.links, ...remote.links]; + final held = {}; + final heldIds = {...holding}; + for (var grew = true; grew;) { + grew = false; + for (final link in allLinks) { + if (held.contains(link.id)) continue; + if (heldIds.contains(link.id) || + heldIds.contains(link.originId) || + heldIds.contains(link.landingId)) { + held.add(link.id); + heldIds.addAll([link.originId, link.landingId]); + grew = true; + } + } + } + if (held.isEmpty) return (remote, held); + + final localLinks = {for (final link in local.links) link.id: link}; + final remoteLinkIds = {for (final link in remote.links) link.id}; + final links = [ + for (final link in remote.links) + if (!held.contains(link.id)) + link + else if (localLinks[link.id] case final onScreen?) + onScreen, + ]; + for (final (index, link) in local.links.indexed) { + if (held.contains(link.id) && !remoteLinkIds.contains(link.id)) { + links.insert(min(index, links.length), link); + } + } + + final originIds = {for (final link in links) link.originId}; + final landingIds = {for (final link in links) link.landingId}; + return ( + LineUpGraph( + origins: [ + for (final origin in mergeRemoteItems( + current: local.origins, + incoming: remote.origins, + idOf: (origin) => origin.id, + keep: heldIds.contains, + )) + if (originIds.contains(origin.id)) origin, + ], + landings: [ + for (final landing in mergeRemoteItems( + current: local.landings, + incoming: remote.landings, + idOf: (landing) => landing.id, + keep: heldIds.contains, + )) + if (landingIds.contains(landing.id)) landing, + ], + links: links, + ), + held, + ); +} diff --git a/lib/strategy/strategy_cloud_migration.dart b/lib/strategy/strategy_cloud_migration.dart index cc03d4fd..c9e72b81 100644 --- a/lib/strategy/strategy_cloud_migration.dart +++ b/lib/strategy/strategy_cloud_migration.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'package:icarus/collab/collab_models.dart'; import 'package:icarus/collab/cloud_lineup_rows.dart'; import 'package:icarus/collab/cloud_media_models.dart'; +import 'package:icarus/const/line_provider.dart'; import 'package:icarus/providers/drawing_provider.dart'; import 'package:icarus/providers/strategy_page.dart'; import 'package:uuid/uuid.dart'; @@ -73,15 +74,22 @@ void appendMigratedPageOps( buildMigratedElementOp(page.id, elementId, payload, elementOrder++)); } - // [usedLineupIds] holds row ids. An entity whose row id another page - // already took (a page duplicated before cloud sync) gets a fresh id, and - // the links and markers that name it follow along. - final graph = lineUpGraphWithIds( - page.lineUpGraph, - (kind, id) => _uniqueLineupEntityId(kind, id, usedLineupIds), + // One row per lineup, keyed by its id. A lineup whose id another page + // already took (a page duplicated before cloud sync) gets a fresh one. Its + // origin and landing keep their ids: spots are shared within a page only. + final graph = page.lineUpGraph; + final rows = cloudLineupRows( + LineUpGraph( + origins: graph.origins, + landings: graph.landings, + links: [ + for (final link in graph.links) + link.copyWith(id: nextUniqueMigrationId(link.id, usedLineupIds)), + ], + ), ); var lineupOrder = 0; - for (final row in cloudLineupRows(graph)) { + for (final row in rows) { ops.add( LineupAddOp( opId: const Uuid().v4(), @@ -94,14 +102,6 @@ void appendMigratedPageOps( } } -String _uniqueLineupEntityId(String kind, String id, Set usedRowIds) { - var candidate = id; - while (!usedRowIds.add(cloudLineupRowId(kind, candidate))) { - candidate = const Uuid().v4(); - } - return candidate; -} - String nextUniqueMigrationId(String preferredId, Set usedIds) { if (usedIds.add(preferredId)) { return preferredId; diff --git a/lib/strategy/strategy_import_export.dart b/lib/strategy/strategy_import_export.dart index 5f23a826..ae49dd8f 100644 --- a/lib/strategy/strategy_import_export.dart +++ b/lib/strategy/strategy_import_export.dart @@ -16,7 +16,6 @@ import 'package:icarus/collab/convex_strategy_repository.dart'; import 'package:icarus/const/drawing_element.dart'; import 'package:icarus/const/folder_icons.dart'; import 'package:icarus/const/hive_boxes.dart'; -import 'package:icarus/const/line_provider.dart'; import 'package:icarus/const/maps.dart'; import 'package:icarus/const/placed_classes.dart'; import 'package:icarus/const/settings.dart'; @@ -2398,7 +2397,7 @@ class StrategyImportExportService { assetIds.add(element.publicId); } - final lineups = _cloudLineupGraph( + final lineups = lineUpGraphFromRemoteLineups( snapshot.lineupsByPage[page.publicId] ?? const [], ); for (final link in lineups.links) { @@ -2516,7 +2515,7 @@ class StrategyImportExportService { } } - final lineUpGraph = _cloudLineupGraph(lineups); + final lineUpGraph = lineUpGraphFromRemoteLineups(lineups); StrategySettings settings = StrategySettings(); final settingsPayload = fullPage.content.settings; @@ -2585,12 +2584,3 @@ class StrategyImportExportService { ); } } - -/// A cloud page's live lineup rows as the graph its canvas draws. -LineUpGraph _cloudLineupGraph(Iterable lineups) { - return lineUpGraphFromCloudRows([ - for (final lineup in lineups) - if (!lineup.deleted) - CloudLineupRow(publicId: lineup.publicId, payload: lineup.payload), - ]).graph; -} diff --git a/lib/strategy/strategy_page_apply.dart b/lib/strategy/strategy_page_apply.dart index c990dd43..9ae321a7 100644 --- a/lib/strategy/strategy_page_apply.dart +++ b/lib/strategy/strategy_page_apply.dart @@ -14,6 +14,7 @@ import 'package:icarus/providers/text_provider.dart'; import 'package:icarus/providers/utility_provider.dart'; import 'package:icarus/const/line_provider.dart'; import 'package:icarus/providers/collab/active_page_live_sync_models.dart'; +import 'package:icarus/strategy/remote_page_merge.dart'; import 'package:icarus/strategy/strategy_page_models.dart'; Future applyStrategyEditorPageData( @@ -91,23 +92,18 @@ Set mergeRemoteStrategyEditorPageData( ref.read(placedImageProvider.notifier).mergeRemote(data.images, keep); ref.read(utilityProvider.notifier).mergeRemote(data.utilities, keep); - // Links name their origin and landing, so the lineup graph changes as one - // piece: all of it, or none while the user holds part of it. - final lineupChanges = { + final (lineUpGraph, heldLineups) = mergeHeldLineups( + local: ref.read(lineUpProvider).graph, + remote: data.lineUpGraph.deepCopy(), + holding: holding, + ); + ref.read(lineUpProvider.notifier).mergeRemote(lineUpGraph); + heldBack.addAll({ for (final key in changed) - if (key.kind == EntitySyncKeyKind.lineup) key, - }; - final graph = ref.read(lineUpProvider).graph; - final lineupIds = { - for (final origin in graph.origins) origin.id, - for (final landing in graph.landings) landing.id, - for (final link in graph.links) link.id, - }; - if (!holding.any(lineupIds.contains)) { - ref.read(lineUpProvider.notifier).fromHive(data.lineUpGraph); - } else { - heldBack.addAll(lineupChanges); - } + if (key.kind == EntitySyncKeyKind.lineup && + heldLineups.contains(key.entityId)) + key, + }); ref .read(mapProvider.notifier) diff --git a/lib/strategy/strategy_page_source.dart b/lib/strategy/strategy_page_source.dart index e4b8a80d..6f700dab 100644 --- a/lib/strategy/strategy_page_source.dart +++ b/lib/strategy/strategy_page_source.dart @@ -280,11 +280,7 @@ class CloudStrategyPageSource implements StrategyPageSource { } } - final lineUpGraph = lineUpGraphFromCloudRows([ - for (final lineup in lineups) - if (!lineup.deleted) - CloudLineupRow(publicId: lineup.publicId, payload: lineup.payload), - ]).graph; + final lineUpGraph = lineUpGraphFromRemoteLineups(lineups); final mapValue = Maps.mapNames.entries.firstWhere( (entry) => entry.value == snapshot.header.mapData, @@ -455,8 +451,12 @@ class CloudStrategyPageSource implements StrategyPageSource { final lineUpGraph = lineUpGraphFromCloudRows([ for (final lineup in projected.lineups) - CloudLineupRow(publicId: lineup.publicId, payload: lineup.payload), - ]).graph; + CloudLineupRow( + publicId: lineup.publicId, + payload: lineup.payload, + revision: lineup.revision, + ), + ]); final mapValue = Maps.mapNames.entries.firstWhere( (entry) => entry.value == snapshot.header.mapData, diff --git a/test/cloud_lineup_rows_test.dart b/test/cloud_lineup_rows_test.dart index 071e808f..c3810829 100644 --- a/test/cloud_lineup_rows_test.dart +++ b/test/cloud_lineup_rows_test.dart @@ -2,60 +2,94 @@ import 'dart:convert'; import 'package:flutter_test/flutter_test.dart'; import 'package:icarus/collab/cloud_lineup_rows.dart'; +import 'package:icarus/collab/collab_models.dart'; import 'package:icarus/const/agents.dart'; import 'package:icarus/const/line_provider.dart'; import 'package:icarus/const/placed_classes.dart'; -LineUpOrigin _origin(String id) => LineUpOrigin( +LineUpOrigin _origin(String id, {Offset position = const Offset(10, 20)}) => + LineUpOrigin( id: id, agent: PlacedAgent( id: 'agent-$id', type: AgentType.sova, - position: const Offset(10, 20), + position: position, lineUpID: id, ), ); -LineUpLanding _landing(String id) => LineUpLanding( +LineUpLanding _landing(String id, {Offset position = const Offset(30, 40)}) => + LineUpLanding( id: id, ability: PlacedAbility( id: 'ability-$id', data: AgentData.agents[AgentType.sova]!.abilities[2], - position: const Offset(30, 40), + position: position, lineUpID: id, ), ); -LineUpGraph _fanIn() => LineUpGraph( +/// Two origins into one landing, and a second lineup out of origin a. +LineUpGraph _shared() => LineUpGraph( origins: [_origin('a'), _origin('b')], - landings: [_landing('shared')], + landings: [_landing('shared'), _landing('other')], links: [ - LineUpLink(id: 'k1', originId: 'a', landingId: 'shared', name: 'A'), + LineUpLink( + id: 'k1', + originId: 'a', + landingId: 'shared', + name: 'A', + notes: 'jump throw', + images: [SimpleImageData(id: 'img-1', fileExtension: '.png')], + ), LineUpLink(id: 'k2', originId: 'b', landingId: 'shared', name: 'B'), + LineUpLink(id: 'k3', originId: 'a', landingId: 'other', name: 'C'), ], ); +String _json(LineUpGraph graph) => jsonEncode(graph.toJson()); + +CloudLineupRow _withRevision(CloudLineupRow row, int revision) => + CloudLineupRow( + publicId: row.publicId, payload: row.payload, revision: revision); + void main() { TestWidgetsFlutterBinding.ensureInitialized(); - test('rows are keyed by kind and entity id and read back whole', () { - final rows = cloudLineupRows(_fanIn()); + test('each lineup is one row, carrying its whole origin and landing', () { + final rows = cloudLineupRows(_shared()); - expect(rows.map((row) => row.publicId), [ - 'lineupOrigin:a', - 'lineupOrigin:b', - 'lineupLanding:shared', - 'lineupLink:k1', - 'lineupLink:k2', + expect(rows.map((row) => row.publicId), ['k1', 'k2', 'k3']); + final first = rows.first.payload; + expect(first['kind'], cloudLineupPayloadKind); + expect(first['payloadVersion'], currentCloudPayloadVersion); + final data = first['data'] as Map; + expect(data.keys.toSet(), { + 'id', + 'name', + 'youtubeLink', + 'notes', + 'images', + 'origin', + 'landing', + }); + expect(data['origin'], jsonDecode(jsonEncode(_origin('a').toJson()))); + expect( + data['landing'], jsonDecode(jsonEncode(_landing('shared').toJson()))); + expect(data['images'], [ + {'id': 'img-1', 'fileExtension': '.png'}, ]); - final read = lineUpGraphFromCloudRows(rows); - expect(read.drawnRowIds, rows.map((row) => row.publicId).toSet()); - expect(jsonEncode(read.graph.toJson()), jsonEncode(_fanIn().toJson())); }); - test('only whole lineups are drawn', () { + test('shared spots read back as one spot, sharing intact', () { + final read = lineUpGraphFromCloudRows(cloudLineupRows(_shared())); + + expect(_json(read), _json(_shared())); + }); + + test('a link missing its origin or landing has no row', () { final rows = cloudLineupRows(LineUpGraph( - origins: [_origin('a'), _origin('lonely')], + origins: [_origin('a')], landings: [_landing('l')], links: [ LineUpLink(id: 'k1', originId: 'a', landingId: 'l'), @@ -63,55 +97,149 @@ void main() { ], )); - final read = lineUpGraphFromCloudRows(rows); + expect(rows.map((row) => row.publicId), ['k1']); + }); - expect(read.drawnRowIds, { - 'lineupOrigin:a', - 'lineupLanding:l', - 'lineupLink:k1', - }); - expect(read.graph.origins.map((origin) => origin.id), ['a']); - expect(read.graph.links.map((link) => link.id), ['k1']); + test('disagreeing copies of a spot draw the highest revision, then id', () { + final moved = LineUpGraph( + origins: [_origin('a'), _origin('b')], + landings: [_landing('shared', position: const Offset(99, 99))], + links: _shared().links.take(2).toList(), + ); + final original = cloudLineupRows(_shared()); + final edited = cloudLineupRows(moved); + Offset landingOf(LineUpGraph graph) => + graph.landings.firstWhere((l) => l.id == 'shared').ability.position; + + // k2 carries the moved copy at a higher revision than k1. + var read = lineUpGraphFromCloudRows([ + _withRevision(original[0], 3), + _withRevision(edited[1], 4), + ]); + expect(landingOf(read), const Offset(99, 99)); + + // Same rows, the other copy ahead: the order rows arrive in never + // decides. + read = lineUpGraphFromCloudRows([ + _withRevision(edited[1], 2), + _withRevision(original[0], 3), + ]); + expect(landingOf(read), const Offset(30, 40)); + + // A tie goes to the greatest publicId (k2 over k1). + read = lineUpGraphFromCloudRows([ + _withRevision(original[0], 5), + _withRevision(edited[1], 5), + ]); + expect(landingOf(read), const Offset(99, 99)); + read = lineUpGraphFromCloudRows([ + _withRevision(edited[1], 5), + _withRevision(original[0], 5), + ]); + expect(landingOf(read), const Offset(99, 99)); + // The spot keeps the place its first row gave it. + expect(read.landings.map((landing) => landing.id), ['shared']); }); - test('a row of any other kind fails loudly, naming the row', () { - // Legacy lineup groups included: they are not part of the cloud format. - for (final kind in ['lineupSomething', 'lineupGroup']) { + test('drawn payloads carry the copy that is drawn', () { + final moved = LineUpGraph( + origins: [_origin('a'), _origin('b')], + landings: [_landing('shared', position: const Offset(99, 99))], + links: _shared().links.take(2).toList(), + ); + final stale = _withRevision(cloudLineupRows(_shared())[0], 1); + final fresh = _withRevision(cloudLineupRows(moved)[1], 2); + + final drawn = drawnCloudLineupPayloads([stale, fresh]); + + expect(drawn.keys, ['k1', 'k2']); + expect(drawn['k2'], fresh.payload); + expect( + (drawn['k1']!['data'] as Map)['landing'], + (fresh.payload['data'] as Map)['landing'], + ); + }); + + test('a row of any other shape fails loudly, naming the row', () { + final valid = cloudLineupRows(_shared()).first.payload; + final data = valid['data'] as Map; + final broken = { + // The graph rows of protocol 4 and the legacy group are not lineups. + 'lineupLink': {...valid, 'kind': 'lineupLink'}, + 'lineupGroup': {...valid, 'kind': 'lineupGroup'}, + 'no origin': { + ...valid, + 'data': {...data}..remove('origin'), + }, + 'no landing': { + ...valid, + 'data': {...data}..remove('landing'), + }, + }; + for (final MapEntry(key: reason, value: payload) in broken.entries) { expect( () => lineUpGraphFromCloudRows([ - CloudLineupRow( - publicId: 'odd', - payload: {'kind': kind, 'payloadVersion': 1, 'data': {}}, - ), + CloudLineupRow(publicId: 'k1', payload: payload), ]), throwsA(isA().having( (error) => error.message, 'message', - contains('Cloud lineup odd could not be read'), + contains('Cloud lineup k1 could not be read'), )), + reason: reason, ); } + expect( + () => lineUpGraphFromCloudRows([ + CloudLineupRow(publicId: 'someone-else', payload: valid), + ]), + throwsA(isA()), + reason: 'a row keyed apart from its lineup', + ); }); - test('new ids carry every reference with them', () { - var next = 0; - final copy = lineUpGraphWithIds(_fanIn(), (kind, id) => '$kind-${next++}'); - - final originIds = copy.origins.map((origin) => origin.id).toSet(); - final landingId = copy.landings.single.id; - expect(originIds.intersection({'a', 'b'}), isEmpty); - for (final origin in copy.origins) { - expect(origin.agent.lineUpID, origin.id); - } - expect(copy.landings.single.ability.lineUpID, landingId); - for (final link in copy.links) { - expect(originIds, contains(link.originId)); - expect(link.landingId, landingId); + test('ops in the cloud format before one row per lineup are retired', () { + final payload = cloudLineupRows(_shared()).first.payload; + expect( + isRetiredCloudLineupOp(LineupAddOp( + opId: 'op', + lineupPublicId: 'k1', + pagePublicId: 'p', + payload: payload, + sortIndex: 0, + )), + isFalse, + ); + expect( + isRetiredCloudLineupOp(LineupAddOp( + opId: 'op', + lineupPublicId: 'lineupLink:k1', + pagePublicId: 'p', + payload: {...payload, 'kind': 'lineupLink'}, + sortIndex: 0, + )), + isTrue, + ); + for (final key in ['lineupOrigin:a', 'lineupLanding:l', 'lineupLink:k']) { + expect( + isRetiredCloudLineupOp(LineupDeleteOp( + opId: 'op', + lineupPublicId: key, + pagePublicId: 'p', + expectedLineupRevision: 1, + )), + isTrue, + reason: key, + ); } - expect(copy.links.map((link) => link.name), ['A', 'B']); expect( - lineUpGraphFromCloudRows(cloudLineupRows(copy)).graph.links, - hasLength(2), + isRetiredCloudLineupOp(ElementDeleteOp( + opId: 'op', + elementPublicId: 'lineupLink:not-a-lineup', + pagePublicId: 'p', + expectedElementRevision: 1, + )), + isFalse, ); }); } diff --git a/test/cloud_payload_upgrade_test.dart b/test/cloud_payload_upgrade_test.dart index dcb9af1e..2a5d9327 100644 --- a/test/cloud_payload_upgrade_test.dart +++ b/test/cloud_payload_upgrade_test.dart @@ -94,24 +94,34 @@ void main() { expect(expected.position, isNot(paranoia.position)); }); - test('a version 1 lineup landing moves its Paranoia', () { - final landing = LineUpLanding(id: 'landing', ability: _placed('l', 1)); - final upgraded = upgradeCloudPayload( - { - ...cloudLineupPayload( - kind: CloudLineupKind.landing, - data: landing.toJson(), + test('a lineup carrying a Paranoia is never moved', () { + // Every lineup row was written at the in-game size: correcting one would + // move its Paranoia twice. + final lineup = cloudLineupRows(LineUpGraph( + origins: [ + LineUpOrigin( + id: 'origin', + agent: PlacedAgent( + id: 'omen', + type: AgentType.omen, + position: const Offset(340, 210), + lineUpID: 'origin', + ), ), - 'payloadVersion': 1, - }, - _map, - ); - - expect(upgraded['payloadVersion'], paranoiaCloudPayloadVersion); - expect( - LineUpLanding.fromJson(cloudPayloadData(upgraded)).ability.position, - ParanoiaRangeMigration.migrateAbility(landing.ability, _map).position, + ], + landings: [LineUpLanding(id: 'landing', ability: _placed('l', 1))], + links: [ + LineUpLink(id: 'lineup', originId: 'origin', landingId: 'landing'), + ], + )).single.payload; + + expect(lineup['payloadVersion'], currentCloudPayloadVersion); + expect(identical(upgradeCloudPayload(lineup, _map), lineup), isTrue); + final page = upgradeRemotePageSnapshot( + _page([_v1('ability', _placed('paranoia', 1).toJson())], [lineup]), + Maps.mapNames[_map]!, ); + expect(identical(page.lineups.single.payload, lineup), isTrue); }); test('rows without a Paranoia are left exactly as the server holds them', () { diff --git a/test/collab/cloud_sync_error_message_test.dart b/test/collab/cloud_sync_error_message_test.dart index 102eb60a..c1d6ab11 100644 --- a/test/collab/cloud_sync_error_message_test.dart +++ b/test/collab/cloud_sync_error_message_test.dart @@ -30,23 +30,18 @@ void main() { expect(message, isNot(contains('LINEUP_PAGE_MISMATCH'))); }); - test('explains a lineup whose origin or landing is gone', () { - final message = friendlyCloudSyncError( - "ConvexFunctionException(LINEUP_LINK_END_MISSING, This lineup's origin " - 'or landing spot is no longer on the page)', - ); + test('explains a lineup change saved in the old cloud format', () { + final message = friendlyCloudSyncError(retiredLineupOpMessage); - expect(message, contains("origin or landing spot isn't on this page")); - expect(message, contains('was not saved')); - expect(message, contains('Keep mine tries again')); - expect(message, contains('Use cloud drops your change')); - expect(message, isNot(contains('LINEUP_LINK_END_MISSING'))); + expect(message, contains('older version of Icarus')); + expect(message, contains('cannot be sent')); + expect(message, contains('Use cloud')); + expect(message, isNot(contains('Keep mine'))); }); test('lineup refusals and oversized work are specific attention reasons', () { - expect(isSpecificAttentionReason(lineupLinkEndMissingMessage), isTrue); expect(isSpecificAttentionReason(lineupPageMismatchMessage), isTrue); - expect(isSpecificAttentionReason(lineupEndInUseMessage), isTrue); + expect(isSpecificAttentionReason(retiredLineupOpMessage), isTrue); expect(isSpecificAttentionReason(pageDeletedMessage), isTrue); expect(isSpecificAttentionReason(cloudOperationTooLargeMessage), isTrue); for (final reason in [ @@ -58,19 +53,6 @@ void main() { } }); - test('explains an origin or landing another lineup still uses', () { - final message = friendlyCloudSyncError( - 'ConvexFunctionException(LINEUP_END_IN_USE, Another lineup still uses ' - 'this origin or landing spot)', - ); - - expect(message, contains('still uses this origin or landing spot')); - expect(message, contains('was not deleted')); - expect(message, contains('Keep mine tries again')); - expect(message, contains('Use cloud brings back that lineup')); - expect(message, isNot(contains('LINEUP_END_IN_USE'))); - }); - test('does not expose unknown transport details', () { final message = friendlyCloudSyncError('socket exploded at 10.0.0.4'); diff --git a/test/collab_sync_models_test.dart b/test/collab_sync_models_test.dart index 5bf4ba94..4ed1e82c 100644 --- a/test/collab_sync_models_test.dart +++ b/test/collab_sync_models_test.dart @@ -1,9 +1,9 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:icarus/collab/canonical_json.dart'; +import 'package:icarus/collab/cloud_lineup_rows.dart'; import 'package:icarus/collab/cloud_media_models.dart'; import 'package:icarus/collab/collab_models.dart'; import 'package:icarus/const/json_converters.dart'; -import 'package:icarus/const/line_provider.dart'; import 'package:icarus/providers/collab/cloud_collab_provider.dart'; import 'package:icarus/strategy/strategy_import_export.dart'; @@ -55,7 +55,7 @@ void main() { ); final json = op.toConvexJson(); - expect(currentCloudProtocolVersion, 4); + expect(currentCloudProtocolVersion, 5); expect(json['type'], 'pageContent.patch'); expect(json['expectedPageContentRevision'], 4); expect(json.containsKey('expectedSequence'), isFalse); @@ -395,21 +395,39 @@ void main() { test('cloud payload data normalizes nested bridge maps for lineup parsing', () { final payload = { - 'kind': 'lineupLanding', + 'kind': cloudLineupPayloadKind, 'payloadVersion': 1, 'data': { - 'id': 'landing-1', - 'ability': { - 'id': 'ability-1', - 'data': {'type': 'sova', 'index': 2.0}, - 'position': {'dx': 30, 'dy': 40}, - 'lineUpID': 'landing-1', + 'id': 'lineup-1', + 'name': 'Garage smoke', + 'origin': { + 'id': 'origin-1', + 'agent': { + 'id': 'agent-1', + 'type': 'sova', + 'position': {'dx': 10, 'dy': 20}, + 'lineUpID': 'origin-1', + }, + }, + 'landing': { + 'id': 'landing-1', + 'ability': { + 'id': 'ability-1', + 'data': {'type': 'sova', 'index': 2.0}, + 'position': {'dx': 30, 'dy': 40}, + 'lineUpID': 'landing-1', + }, }, }, }; - final landing = LineUpLanding.fromJson(cloudPayloadData(payload)); + final graph = lineUpGraphFromCloudRows([ + CloudLineupRow(publicId: 'lineup-1', payload: payload), + ]); + final landing = graph.landings.single; + expect(graph.links.single.name, 'Garage smoke'); + expect(graph.origins.single.agent.lineUpID, 'origin-1'); expect(landing.id, 'landing-1'); expect(landing.ability.lineUpID, 'landing-1'); expect(landing.ability.position, const Offset(30, 40)); @@ -485,8 +503,8 @@ void main() { ); expect( - () => StrategyImportExportService.strategyDataFromRemoteSnapshot( - snapshot), + () => + StrategyImportExportService.strategyDataFromRemoteSnapshot(snapshot), throwsA( isA().having( (error) => error.message, diff --git a/test/global_strategy_outbox_test.dart b/test/global_strategy_outbox_test.dart index 334ee03d..c0558107 100644 --- a/test/global_strategy_outbox_test.dart +++ b/test/global_strategy_outbox_test.dart @@ -3,7 +3,6 @@ import 'dart:convert'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:icarus/collab/cloud_lineup_rows.dart'; import 'package:icarus/collab/collab_models.dart'; import 'package:icarus/collab/convex_strategy_repository.dart'; import 'package:icarus/collab/durable_strategy_outbox.dart'; @@ -244,53 +243,37 @@ void main() { expect(finalOp.expectedElementRevision, 2); }); - test('a link waiting for a backing-off origin does not hold up the drain', - () async { + test('a lineup backing off does not hold up the drain', () async { final store = MemoryDurableStrategyOutboxStore(); - DurableOutboxRecord lineup( - String kind, - Map data, { - required DateTime updatedAt, - int attempts = 0, - }) { - final op = LineupAddOp( - opId: 'add-$kind', - lineupPublicId: cloudLineupRowId(kind, data['id'] as String), - pagePublicId: 'page-one', - payload: cloudLineupPayload(kind: kind, data: data), - sortIndex: 0, - ); - return DurableOutboxRecord( - accountId: 'account-a', - strategyPublicId: 'blocked', - entityKey: EntitySyncKey.forStrategyOp(op)!, - pending: PendingOp( - op: op, - clientId: 'client-blocked', - attempts: attempts, - lastAttemptAt: attempts == 0 ? null : DateTime.now(), - ), - status: DurableOutboxStatus.queued, - createdAt: updatedAt, - updatedAt: updatedAt, - ); - } - - // The oldest work is a link whose origin just failed to send and is - // backing off; the link must wait for it. - await store.put(lineup( - CloudLineupKind.link, - {'id': 'k', 'originId': 'o', 'landingId': 'l'}, - updatedAt: DateTime(2025), - )); - await store.put(lineup( - CloudLineupKind.origin, - {'id': 'o', 'agent': {}}, + final lineup = LineupAddOp( + opId: 'add-lineup', + lineupPublicId: 'k', + pagePublicId: 'page-one', + payload: cloudLineupPayload({ + 'id': 'k', + 'origin': {'id': 'o', 'agent': {}}, + 'landing': {'id': 'l', 'ability': {}}, + }), + sortIndex: 0, + ); + // The oldest work is a lineup that just failed to send and is backing + // off. + await store.put(DurableOutboxRecord( + accountId: 'account-a', + strategyPublicId: 'blocked', + entityKey: EntitySyncKey.forStrategyOp(lineup)!, + pending: PendingOp( + op: lineup, + clientId: 'client-blocked', + attempts: 3, + lastAttemptAt: DateTime.now(), + ), + status: DurableOutboxStatus.queued, + createdAt: DateTime(2025), updatedAt: DateTime(2025), - attempts: 3, )); // The first drain after sign-in ignores backoff; this older work takes - // it, so the drains after it honour the origin's backoff. + // it, so the drains after it honour the lineup's backoff. await store.put(_record(strategyId: 'first', opId: 'first') .copyWith(updatedAt: DateTime(2024))); await store.put(_record(strategyId: 'open', opId: 'open')); @@ -305,8 +288,8 @@ void main() { await _waitUntil(() => repository.calls.length == 2); expect(repository.calls.map((call) => call.strategyId), ['first', 'open']); expect( - store.load().records.map((record) => record.pending.op.opId).toSet(), - {'add-${CloudLineupKind.link}', 'add-${CloudLineupKind.origin}'}, + store.load().records.map((record) => record.pending.op.opId), + ['add-lineup'], ); }); diff --git a/test/remote_page_merge_test.dart b/test/remote_page_merge_test.dart index bd8935c0..2d3b856d 100644 --- a/test/remote_page_merge_test.dart +++ b/test/remote_page_merge_test.dart @@ -1,6 +1,39 @@ +import 'package:flutter/painting.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:icarus/const/agents.dart'; +import 'package:icarus/const/line_provider.dart'; +import 'package:icarus/const/placed_classes.dart'; import 'package:icarus/strategy/remote_page_merge.dart'; +LineUpOrigin _origin(String id, double x) => LineUpOrigin( + id: id, + agent: PlacedAgent( + id: 'agent-$id', + type: AgentType.sova, + position: Offset(x, 0), + lineUpID: id, + ), + ); + +LineUpLanding _landing(String id, double x) => LineUpLanding( + id: id, + ability: PlacedAbility( + id: 'ability-$id', + data: AgentData.agents[AgentType.sova]!.abilities[2], + position: Offset(x, 0), + lineUpID: id, + ), + ); + +LineUpLink _link(String id, String originId, String landingId, + [String? name]) => + LineUpLink( + id: id, + originId: originId, + landingId: landingId, + name: name ?? id, + ); + void main() { List merge( List current, List incoming, Set held) => @@ -30,4 +63,93 @@ void main() { test('an unheld item the server removed goes', () { expect(merge(['a:1', 'b:1'], ['b:1'], {'b'}), ['b:1']); }); + + group('held lineups', () { + // On screen: k1 (o1 → shared) and k2 (o2 → shared) share a landing; k3 + // (o3 → l3) stands apart. The server moved every spot to 9 and renamed + // every lineup. + final local = LineUpGraph( + origins: [_origin('o1', 1), _origin('o2', 1), _origin('o3', 1)], + landings: [_landing('shared', 1), _landing('l3', 1)], + links: [ + _link('k1', 'o1', 'shared'), + _link('k2', 'o2', 'shared'), + _link('k3', 'o3', 'l3'), + ], + ); + final remote = LineUpGraph( + origins: [_origin('o1', 9), _origin('o2', 9), _origin('o3', 9)], + landings: [_landing('shared', 9), _landing('l3', 9)], + links: [ + _link('k1', 'o1', 'shared', 'k1 renamed'), + _link('k2', 'o2', 'shared', 'k2 renamed'), + _link('k3', 'o3', 'l3', 'k3 renamed'), + ], + ); + Map names(LineUpGraph graph) => + {for (final link in graph.links) link.id: link.name}; + Map spots(LineUpGraph graph) => { + for (final origin in graph.origins) + origin.id: origin.agent.position.dx, + for (final landing in graph.landings) + landing.id: landing.ability.position.dx, + }; + + test('nothing held takes the server copy whole', () { + final (graph, held) = + mergeHeldLineups(local: local, remote: remote, holding: {'x'}); + + expect(held, isEmpty); + expect(identical(graph, remote), isTrue); + }); + + test('holding a spot holds every lineup sharing it, and only those', () { + final (graph, held) = + mergeHeldLineups(local: local, remote: remote, holding: {'o1'}); + + // o1 holds k1; k1 shares its landing with k2, so k2 waits too. + expect(held, {'k1', 'k2'}); + expect(names(graph), {'k1': 'k1', 'k2': 'k2', 'k3': 'k3 renamed'}); + expect(spots(graph), { + 'o1': 1, + 'o2': 1, + 'o3': 9, + 'shared': 1, + 'l3': 9, + }); + // Held objects stay the ones on screen. + expect(identical(graph.origins.first, local.origins.first), isTrue); + }); + + test('a teammate lineup new on a held spot waits off the canvas', () { + final withNew = LineUpGraph( + origins: [...remote.origins, _origin('o4', 9)], + landings: remote.landings, + links: [...remote.links, _link('k4', 'o4', 'l3')], + ); + + final (graph, held) = + mergeHeldLineups(local: local, remote: withNew, holding: {'l3'}); + + expect(held, {'k3', 'k4'}); + expect(graph.links.map((link) => link.id), ['k1', 'k2', 'k3']); + expect(graph.origins.map((origin) => origin.id), ['o1', 'o2', 'o3']); + expect(spots(graph)['l3'], 1); + }); + + test('a held lineup the server removed stays in its place', () { + final without = LineUpGraph( + origins: [remote.origins[1], remote.origins[2]], + landings: remote.landings, + links: [remote.links[1], remote.links[2]], + ); + + final (graph, held) = + mergeHeldLineups(local: local, remote: without, holding: {'k1'}); + + expect(held, {'k1', 'k2'}); + expect(graph.links.map((link) => link.id), ['k1', 'k2', 'k3']); + expect(spots(graph)['o1'], 1); + }); + }); } diff --git a/test/strategy_cloud_migration_test.dart b/test/strategy_cloud_migration_test.dart index e9e1db1e..511785e9 100644 --- a/test/strategy_cloud_migration_test.dart +++ b/test/strategy_cloud_migration_test.dart @@ -83,7 +83,7 @@ LineUpGraph _hydrate(List adds) { publicId: add.lineupPublicId, payload: jsonDecode(jsonEncode(add.payload)) as Map, ), - ]).graph; + ]); } String _canonical(Object? value) => @@ -92,20 +92,15 @@ String _canonical(Object? value) => void main() { TestWidgetsFlutterBinding.ensureInitialized(); - test('a lineup is uploaded as one row per origin, landing and link', () { + test('a lineup is uploaded as one row carrying its origin and landing', () { final adds = _upload(_sharedIdLineup()); - expect(adds.map((add) => add.lineupPublicId), [ - 'lineupOrigin:origin-1', - 'lineupLanding:link-1', - 'lineupLink:link-1', - ]); - expect(adds.map((add) => add.payload['kind']), [ - 'lineupOrigin', - 'lineupLanding', - 'lineupLink', - ]); - expect(adds.map((add) => add.sortIndex), [0, 1, 2]); + expect(adds.map((add) => add.lineupPublicId), ['link-1']); + expect(adds.single.payload['kind'], cloudLineupPayloadKind); + expect(adds.single.sortIndex, 0); + final data = cloudPayloadData(adds.single.payload); + expect((data['origin'] as Map)['id'], 'origin-1'); + expect((data['landing'] as Map)['id'], 'link-1'); // Hydrating the upload gives back the page's graph, ids and all, so the // page never authors a patch for a lineup nobody touched. expect( @@ -114,22 +109,20 @@ void main() { ); }); - test('an id another page already took is reassigned with its references', () { + test('a lineup id another page already took is reassigned', () { final adds = _upload( _sharedIdLineup(), - // A page duplicated from page 1 already uploaded this origin. - usedLineupIds: {'lineupOrigin:origin-1'}, + // A page duplicated from page 1 already uploaded this lineup. + usedLineupIds: {'link-1'}, ); - final origin = cloudPayloadData(adds[0].payload); - final newOriginId = origin['id'] as String; - expect(newOriginId, isNot('origin-1')); - expect(adds[0].lineupPublicId, 'lineupOrigin:$newOriginId'); - expect((origin['agent'] as Map)['lineUpID'], newOriginId); - // The landing and link ids were free, so they stay. - expect(adds[1].lineupPublicId, 'lineupLanding:link-1'); - expect(adds[2].lineupPublicId, 'lineupLink:link-1'); - expect(cloudPayloadData(adds[2].payload)['originId'], newOriginId); + final newId = adds.single.lineupPublicId; + expect(newId, isNot('link-1')); + final data = cloudPayloadData(adds.single.payload); + expect(data['id'], newId); + // Spots are shared within a page only, so its ends keep their ids. + expect((data['origin'] as Map)['id'], 'origin-1'); + expect((data['landing'] as Map)['id'], 'link-1'); // And the rows reproduce exactly when the hydrated graph is sent again. final resent = cloudLineupRows(_hydrate(adds)); @@ -139,13 +132,13 @@ void main() { ); }); - test('a fan-in lineup uploads with its shared landing and link names', () { + test('fan-in and fan-out upload with their shared spots and names', () { final fanIn = LineUpGraph( origins: [ _origin('origin-a', const Offset(10, 20)), _origin('origin-b', const Offset(50, 60)), ], - landings: [_landing('shared')], + landings: [_landing('shared'), _landing('other')], links: [ LineUpLink( id: 'link-a', @@ -160,17 +153,37 @@ void main() { landingId: 'shared', name: 'From mid', ), + LineUpLink( + id: 'link-c', + originId: 'origin-a', + landingId: 'other', + name: 'From heaven, long', + ), ], ); final adds = _upload(fanIn); - expect(adds, hasLength(5)); + expect(adds.map((add) => add.lineupPublicId), [ + 'link-a', + 'link-b', + 'link-c', + ]); final hydrated = _hydrate(adds); - expect(hydrated.landings.single.id, 'shared'); + expect(hydrated.landings.map((landing) => landing.id), [ + 'shared', + 'other', + ]); expect( - [for (final link in hydrated.links) (link.landingId, link.name)], - [('shared', 'From heaven'), ('shared', 'From mid')], + [ + for (final link in hydrated.links) + (link.originId, link.landingId, link.name), + ], + [ + ('origin-a', 'shared', 'From heaven'), + ('origin-b', 'shared', 'From mid'), + ('origin-a', 'other', 'From heaven, long'), + ], ); expect(hydrated.links.first.images.single.id, 'image-a'); expect(_canonical(hydrated.toJson()), _canonical(fanIn.toJson())); diff --git a/test/strategy_op_queue_provider_test.dart b/test/strategy_op_queue_provider_test.dart index eb845888..0bf60640 100644 --- a/test/strategy_op_queue_provider_test.dart +++ b/test/strategy_op_queue_provider_test.dart @@ -3,7 +3,6 @@ import 'dart:convert'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:icarus/collab/cloud_lineup_rows.dart'; import 'package:icarus/collab/cloud_sync_error_message.dart'; import 'package:icarus/collab/collab_models.dart'; import 'package:icarus/collab/convex_strategy_repository.dart'; @@ -937,180 +936,42 @@ void main() { expect(store.load().records.single.pending.op.opId, op.opId); }); - test('a lineup link waits for the origin and landing still to be sent', + test('a lineup refused for another page stays saved and keeps that reason', () async { final store = MemoryDurableStrategyOutboxStore(); - DurableOutboxRecord record(StrategyOp op, String clientId) => - DurableOutboxRecord( - accountId: 'account-a', - strategyPublicId: 'strategy-1', - entityKey: EntitySyncKey.forStrategyOp(op)!, - pending: PendingOp(op: op, clientId: clientId), - status: DurableOutboxStatus.queued, - createdAt: DateTime(2026), - updatedAt: DateTime(2026), - ); - LineupAddOp add(String kind, Map data) => LineupAddOp( - opId: 'add-$kind', - lineupPublicId: cloudLineupRowId(kind, data['id'] as String), - pagePublicId: 'page-1', - payload: cloudLineupPayload(kind: kind, data: data), - sortIndex: 0, - ); - // As an outbox reloads after a restart: by key, so the link comes - // before the origin, and the origin was queued by an earlier session - // under another client id. - await store.put(record(_cloudElementOp(), 'client-b')); - await store.put(record( - add(CloudLineupKind.landing, {'id': 'l', 'ability': {}}), - 'client-b', - )); - await store.put(record( - add(CloudLineupKind.link, { - 'id': 'k', - 'originId': 'o', - 'landingId': 'l', - }), - 'client-b', - )); - await store.put(record( - add(CloudLineupKind.origin, {'id': 'o', 'agent': {}}), - 'client-a', - )); - final repository = _RecordingAckRepository(); - final container = _cloudQueueContainer( - store: store, - repository: repository, - ); - addTearDown(container.dispose); - final notifier = container.read(strategyOpQueueProvider.notifier) - ..setActiveStrategy('strategy-1', accountId: 'account-a'); - - await notifier.flushNow(); - for (var i = 0; i < 50 && repository.calls.length < 3; i++) { - await Future.delayed(Duration.zero); - } - - expect( - repository.calls.map((ops) => ops.map((op) => op.opId).toList()), - [ - ['op-1', 'add-${CloudLineupKind.landing}'], - ['add-${CloudLineupKind.origin}'], - ['add-${CloudLineupKind.link}'], - ], - ); - expect(store.load().records, isEmpty); - }); - - test('keep mine re-sends a link refused for a missing end as it was', - () async { - final store = MemoryDurableStrategyOutboxStore(); - LineupAddOp link(String id, {int? expectedRevision}) => LineupAddOp( - opId: 'refused-$id', - lineupPublicId: cloudLineupRowId(CloudLineupKind.link, id), - pagePublicId: 'page-1', - payload: cloudLineupPayload( - kind: CloudLineupKind.link, - data: {'id': id, 'originId': 'o', 'landingId': 'l'}, - ), - sortIndex: 0, - expectedLineupRevision: expectedRevision, - ); - // A new link, and a deleted one being restored. - final refused = [link('new'), link('restored', expectedRevision: 3)]; - for (final op in refused) { - await store.put(DurableOutboxRecord( - accountId: 'account-a', - strategyPublicId: 'strategy-1', - entityKey: EntitySyncKey.forStrategyOp(op)!, - pending: PendingOp(op: op, clientId: 'client-a'), - status: DurableOutboxStatus.attention, - createdAt: DateTime(2026), - updatedAt: DateTime(2026), - lastError: lineupLinkEndMissingMessage, - )); - } final container = _cloudQueueContainer( store: store, - repository: _RecordingAckRepository(), + repository: _FailingLineupRepository(), ); addTearDown(container.dispose); final notifier = container.read(strategyOpQueueProvider.notifier) ..setActiveStrategy('strategy-1', accountId: 'account-a'); + final lineup = _lineupAdd(opId: 'add-lineup', pageId: 'page-2'); + final key = EntitySyncKey.forStrategyOp(lineup)!; - await notifier.retryRejected(flushImmediately: false); - - final current = container.read(strategyOpQueueProvider); - expect(current.attentionByEntityKey, isEmpty); - for (final op in refused) { - final retried = current - .queuedByEntityKey[EntitySyncKey.forStrategyOp(op)!]!.pending.op; - expect(retried, isA()); - expect(retried.opId, isNot(op.opId)); - expect(retried.expectedRevision, op.expectedRevision); - expect(retried.payload, op.payload); - } - }); - - test('a link refused for a missing end stays saved and needs attention', - () async { - final store = MemoryDurableStrategyOutboxStore(); - final container = _cloudQueueContainer( - store: store, - repository: _MissingLinkEndRepository(), - ); - addTearDown(container.dispose); - final notifier = container.read(strategyOpQueueProvider.notifier) - ..setActiveStrategy('strategy-1', accountId: 'account-a'); - final link = LineupAddOp( - opId: 'add-link', - lineupPublicId: cloudLineupRowId(CloudLineupKind.link, 'k'), - pagePublicId: 'page-1', - payload: cloudLineupPayload( - kind: CloudLineupKind.link, - data: {'id': 'k', 'originId': 'deleted', 'landingId': 'l'}, - ), - sortIndex: 0, - ); - final key = EntitySyncKey.forStrategyOp(link)!; - - await notifier.enqueue(link, flushImmediately: false); + await notifier.enqueue(lineup, flushImmediately: false); await notifier.flushNow(); final current = container.read(strategyOpQueueProvider); - expect(current.attentionByEntityKey[key]!.pending.op.opId, 'add-link'); + expect(current.attentionByEntityKey[key]!.pending.op.opId, 'add-lineup'); expect(current.needsAttention, isTrue); // The sync button explains the refusal, not a generic conflict. - expect(current.lastError, lineupLinkEndMissingMessage); + expect(current.lastError, lineupPageMismatchMessage); + expect(isSpecificAttentionReason(current.lastError!), isTrue); final durable = store.load().records.single; expect(durable.status, DurableOutboxStatus.attention); expect( friendlyCloudSyncError(durable.lastError!), - contains('on this page in the cloud'), + contains('clashes with one on another page'), ); }); test('opening a strategy shows its saved lineup refusal', () async { final store = MemoryDurableStrategyOutboxStore(); - final link = LineupAddOp( - opId: 'refused-link', - lineupPublicId: cloudLineupRowId(CloudLineupKind.link, 'k'), - pagePublicId: 'page-1', - payload: cloudLineupPayload( - kind: CloudLineupKind.link, - data: {'id': 'k', 'originId': 'o', 'landingId': 'l'}, - ), - sortIndex: 0, - ); - await store.put(DurableOutboxRecord( - accountId: 'account-a', - strategyPublicId: 'strategy-1', - entityKey: EntitySyncKey.forStrategyOp(link)!, - pending: PendingOp(op: link, clientId: 'client-a'), + await store.put(_savedRecord( + _lineupAdd(opId: 'refused-lineup'), status: DurableOutboxStatus.attention, - createdAt: DateTime(2026), - updatedAt: DateTime(2026), - lastError: lineupLinkEndMissingMessage, + lastError: lineupPageMismatchMessage, )); final container = _cloudQueueContainer( store: store, @@ -1123,13 +984,13 @@ void main() { notifier.setActiveStrategy('strategy-1', accountId: 'account-a'); expect( container.read(strategyOpQueueProvider).lastError, - lineupLinkEndMissingMessage, + lineupPageMismatchMessage, ); notifier.setActiveStrategy('strategy-2', accountId: 'account-a'); notifier.setActiveStrategy('strategy-1', accountId: 'account-a'); expect( container.read(strategyOpQueueProvider).lastError, - lineupLinkEndMissingMessage, + lineupPageMismatchMessage, ); }); @@ -1138,52 +999,32 @@ void main() { final store = MemoryDurableStrategyOutboxStore(); final container = _cloudQueueContainer( store: store, - repository: _MissingLinkEndRepository(), + repository: _FailingLineupRepository(), ); addTearDown(container.dispose); final notifier = container.read(strategyOpQueueProvider.notifier) ..setActiveStrategy('strategy-1', accountId: 'account-a'); - LineupAddOp link(String name) => LineupAddOp( - opId: 'add-$name', - lineupPublicId: cloudLineupRowId(CloudLineupKind.link, 'k'), - pagePublicId: 'page-1', - payload: cloudLineupPayload( - kind: CloudLineupKind.link, - data: { - 'id': 'k', - 'originId': 'o', - 'landingId': 'l', - 'name': name, - }, - ), - sortIndex: 0, - ); - final key = EntitySyncKey.forStrategyOp(link('first'))!; + LineupAddOp lineup(String name) => + _lineupAdd(opId: 'add-$name', name: name); + final key = EntitySyncKey.forStrategyOp(lineup('first'))!; - await notifier.enqueue(link('first'), flushImmediately: false); + await notifier.enqueue(lineup('first'), flushImmediately: false); await notifier.flushNow(); await notifier.syncDesiredGenericOp( entityKey: key, - desiredOp: link('edited'), + desiredOp: lineup('edited'), ); expect(container.read(strategyOpQueueProvider).successorByEntityKey, contains(key)); await notifier.syncDesiredGenericOp( entityKey: key, - desiredOp: link('first'), + desiredOp: lineup('first'), ); final current = container.read(strategyOpQueueProvider); expect(current.successorByEntityKey, isEmpty); - expect(current.lastError, lineupLinkEndMissingMessage); - expect( - store.load().records.single.lastError, lineupLinkEndMissingMessage); - - // Keep mine still re-sends it as it was. - await notifier.retryRejected(flushImmediately: false); - final retried = container.read(strategyOpQueueProvider); - expect(retried.attentionByEntityKey, isEmpty); - expect(retried.queuedByEntityKey[key]!.pending.op, isA()); + expect(current.lastError, lineupPageMismatchMessage); + expect(store.load().records.single.lastError, lineupPageMismatchMessage); }); // A conflict, and a failed validation that is not one. @@ -1194,16 +1035,6 @@ void main() { test('a lineup refusal beside other refused work notes it ($otherReason)', () async { final store = MemoryDurableStrategyOutboxStore(); - final link = LineupAddOp( - opId: 'refused-link', - lineupPublicId: cloudLineupRowId(CloudLineupKind.link, 'k'), - pagePublicId: 'page-1', - payload: cloudLineupPayload( - kind: CloudLineupKind.link, - data: {'id': 'k', 'originId': 'o', 'landingId': 'l'}, - ), - sortIndex: 0, - ); const element = ElementPatchOp( opId: 'stale-element', elementPublicId: 'element-1', @@ -1212,17 +1043,12 @@ void main() { expectedElementRevision: 1, ); for (final (StrategyOp op, reason) in [ - (link, lineupLinkEndMissingMessage), + (_lineupAdd(opId: 'refused-lineup'), lineupPageMismatchMessage), (element, otherReason), ]) { - await store.put(DurableOutboxRecord( - accountId: 'account-a', - strategyPublicId: 'strategy-1', - entityKey: EntitySyncKey.forStrategyOp(op)!, - pending: PendingOp(op: op, clientId: 'client-a'), + await store.put(_savedRecord( + op, status: DurableOutboxStatus.attention, - createdAt: DateTime(2026), - updatedAt: DateTime(2026), lastError: reason, latestServerRevision: 2, )); @@ -1238,125 +1064,107 @@ void main() { .setActiveStrategy('strategy-1', accountId: 'account-a'); final error = container.read(strategyOpQueueProvider).lastError!; - expect(error, contains(lineupLinkEndMissingMessage)); + expect(error, contains(lineupPageMismatchMessage)); expect(error, contains(otherWorkNeedsAttentionNote)); }); } - test('a lineup refused for another page keeps that reason', () async { - final container = _cloudQueueContainer( - store: MemoryDurableStrategyOutboxStore(), - repository: _MissingLinkEndRepository( - code: 'LINEUP_PAGE_MISMATCH', - message: lineupPageMismatchMessage, - ), + test('an old-format lineup add is shown, never sent, and kept on restart', + () async { + final store = MemoryDurableStrategyOutboxStore(); + final retired = _retiredLinkAdd(); + final retiredKey = EntitySyncKey.forStrategyOp(retired)!; + // Queued by one session, so both are claimed as one batch. + await store.put(_savedRecord(_cloudElementOp())); + await store.put(_savedRecord(retired)); + var repository = _RecordingAckRepository(); + var container = _cloudQueueContainer( + store: store, + repository: repository, ); - addTearDown(container.dispose); - final notifier = container.read(strategyOpQueueProvider.notifier) + var notifier = container.read(strategyOpQueueProvider.notifier) ..setActiveStrategy('strategy-1', accountId: 'account-a'); - await notifier.enqueue( - LineupPatchOp( - opId: 'patch-origin', - lineupPublicId: cloudLineupRowId(CloudLineupKind.origin, 'o'), - pagePublicId: 'page-2', - payload: cloudLineupPayload( - kind: CloudLineupKind.origin, - data: {'id': 'o', 'agent': {}}, - ), - expectedLineupRevision: 1, - ), - flushImmediately: false, - ); await notifier.flushNow(); + await _settle(); - final current = container.read(strategyOpQueueProvider); + // The rest of the batch lands; the old change never leaves the device. + expect( + repository.calls.map((ops) => ops.map((op) => op.opId).toList()), + [ + ['op-1'], + ], + ); + var current = container.read(strategyOpQueueProvider); + expect(current.queuedByEntityKey, isEmpty); + expect(current.attentionByEntityKey.keys, [retiredKey]); expect(current.needsAttention, isTrue); - expect(current.lastError, lineupPageMismatchMessage); + expect(current.lastError, retiredLineupOpMessage); + expect(isSpecificAttentionReason(current.lastError!), isTrue); + final durable = store.load().records.single; + expect(durable.status, DurableOutboxStatus.attention); + expect(durable.pending.op.opId, retired.opId); + expect(durable.pending.op.payload, retired.payload); + expect(durable.lastError, retiredLineupOpMessage); + expect( + friendlyCloudSyncError(durable.lastError!), + contains('older version of Icarus'), + ); + + // After a restart it is still there, still explained, still unsent. + container.dispose(); + repository = _RecordingAckRepository(); + container = _cloudQueueContainer(store: store, repository: repository); + addTearDown(container.dispose); + notifier = container.read(strategyOpQueueProvider.notifier) + ..setActiveStrategy('strategy-1', accountId: 'account-a'); + await notifier.flushNow(); + await _settle(); + + current = container.read(strategyOpQueueProvider); + expect(current.attentionByEntityKey.keys, [retiredKey]); + expect(current.lastError, retiredLineupOpMessage); + expect(repository.calls, isEmpty); + expect(store.load().records.single.pending.op.opId, retired.opId); }); - test('a lineup delete sends its link before its origin and landing', + test('an old-format lineup change refused by an older build says why', () async { final store = MemoryDurableStrategyOutboxStore(); - LineupDeleteOp delete(String kind, String id) => LineupDeleteOp( - opId: 'delete-$kind', - lineupPublicId: cloudLineupRowId(kind, id), - pagePublicId: 'page-1', - expectedLineupRevision: 1, - ); - // As an outbox reloads after a restart: by key, so the landing comes - // before the link. - for (final op in [ - delete(CloudLineupKind.landing, 'l'), - delete(CloudLineupKind.link, 'k'), - delete(CloudLineupKind.origin, 'o'), - ]) { - await store.put(DurableOutboxRecord( - accountId: 'account-a', - strategyPublicId: 'strategy-1', - entityKey: EntitySyncKey.forStrategyOp(op)!, - pending: PendingOp(op: op, clientId: 'client-a'), - status: DurableOutboxStatus.queued, - createdAt: DateTime(2026), - updatedAt: DateTime(2026), - )); - } - final repository = _RecordingAckRepository(); + // An older build left it in attention with its own reason. + await store.put(_savedRecord( + _retiredLinkAdd(), + status: DurableOutboxStatus.attention, + lastError: "This lineup's origin or landing spot is no longer on " + 'the page', + )); final container = _cloudQueueContainer( store: store, - repository: repository, + repository: _RecordingAckRepository(), ); addTearDown(container.dispose); - final notifier = container.read(strategyOpQueueProvider.notifier) - ..setActiveStrategy('strategy-1', accountId: 'account-a'); - await notifier.flushNow(); - for (var i = 0; i < 50 && repository.calls.isEmpty; i++) { - await Future.delayed(Duration.zero); - } + container + .read(strategyOpQueueProvider.notifier) + .setActiveStrategy('strategy-1', accountId: 'account-a'); expect( - repository.calls.map((ops) => ops.map((op) => op.opId).toList()), - [ - [ - 'delete-${CloudLineupKind.link}', - 'delete-${CloudLineupKind.landing}', - 'delete-${CloudLineupKind.origin}', - ], - ], + container.read(strategyOpQueueProvider).lastError, + retiredLineupOpMessage, ); - expect(store.load().records, isEmpty); }); - test('an origin delete waits for a link delete still to be sent', () async { + test('an old-format lineup delete is shown, and alone sends nothing', + () async { final store = MemoryDurableStrategyOutboxStore(); - DurableOutboxRecord record(StrategyOp op, String clientId) => - DurableOutboxRecord( - accountId: 'account-a', - strategyPublicId: 'strategy-1', - entityKey: EntitySyncKey.forStrategyOp(op)!, - pending: PendingOp(op: op, clientId: clientId), - status: DurableOutboxStatus.queued, - createdAt: DateTime(2026), - updatedAt: DateTime(2026), - ); - LineupDeleteOp delete(String kind, String id, {String? pageId}) => - LineupDeleteOp( - opId: 'delete-$kind-$id', - lineupPublicId: cloudLineupRowId(kind, id), - pagePublicId: pageId ?? 'page-1', - expectedLineupRevision: 1, - ); - // The link delete was queued by an earlier session under another - // client id, so it cannot share the origin's batch. An origin on - // another page does not wait for it. - await store.put(record(_cloudElementOp(), 'client-a')); - await store.put(record(delete(CloudLineupKind.origin, 'o'), 'client-a')); - await store.put(record( - delete(CloudLineupKind.origin, 'p2', pageId: 'page-2'), - 'client-a', - )); - await store.put(record(delete(CloudLineupKind.link, 'k'), 'client-b')); + const retired = LineupDeleteOp( + opId: 'old-origin-delete', + lineupPublicId: 'lineupOrigin:o', + pagePublicId: 'page-1', + expectedLineupRevision: 1, + ); + final retiredKey = EntitySyncKey.forStrategyOp(retired)!; + await store.put(_savedRecord(retired)); final repository = _RecordingAckRepository(); final container = _cloudQueueContainer( store: store, @@ -1367,69 +1175,92 @@ void main() { ..setActiveStrategy('strategy-1', accountId: 'account-a'); await notifier.flushNow(); - for (var i = 0; i < 50 && repository.calls.length < 2; i++) { - await Future.delayed(Duration.zero); - } + await _settle(); - expect( - repository.calls.map((ops) => ops.map((op) => op.opId).toList()), - [ - ['delete-${CloudLineupKind.link}-k'], - [ - 'op-1', - 'delete-${CloudLineupKind.origin}-o', - 'delete-${CloudLineupKind.origin}-p2', - ], - ], - ); - expect(store.load().records, isEmpty); + expect(repository.calls, isEmpty); + final current = container.read(strategyOpQueueProvider); + expect(current.queuedByEntityKey, isEmpty); + expect(current.attentionByEntityKey.keys, [retiredKey]); + expect(current.lastError, retiredLineupOpMessage); + final durable = store.load().records.single; + expect(durable.status, DurableOutboxStatus.attention); + expect(durable.pending.op.opId, retired.opId); + expect(durable.lastError, retiredLineupOpMessage); }); - test('an end delete refused because a lineup uses it needs attention', + test('keep mine leaves an old-format lineup change waiting; discard drops', () async { final store = MemoryDurableStrategyOutboxStore(); + final retired = _retiredLinkAdd(); + final retiredKey = EntitySyncKey.forStrategyOp(retired)!; + const element = ElementPatchOp( + opId: 'stale-element', + elementPublicId: 'element-1', + pagePublicId: 'page-1', + payload: {'value': 'mine'}, + expectedElementRevision: 1, + ); + final elementKey = EntitySyncKey.forStrategyOp(element)!; + await store.put(_savedRecord( + element, + status: DurableOutboxStatus.attention, + lastError: 'revision_mismatch', + latestServerRevision: 2, + )); + await store.put(_savedRecord(retired)); + final repository = _RecordingAckRepository(); final container = _cloudQueueContainer( store: store, - repository: _MissingLinkEndRepository( - code: 'LINEUP_END_IN_USE', - message: lineupEndInUseMessage, - ), + repository: repository, ); addTearDown(container.dispose); final notifier = container.read(strategyOpQueueProvider.notifier) ..setActiveStrategy('strategy-1', accountId: 'account-a'); - final delete = LineupDeleteOp( - opId: 'delete-origin', - lineupPublicId: cloudLineupRowId(CloudLineupKind.origin, 'o'), - pagePublicId: 'page-1', - expectedLineupRevision: 1, - ); - final key = EntitySyncKey.forStrategyOp(delete)!; - - await notifier.enqueue(delete, flushImmediately: false); await notifier.flushNow(); + await _settle(); + expect(repository.calls, isEmpty); + expect( + container.read(strategyOpQueueProvider).lastError, + '$retiredLineupOpMessage. $otherWorkNeedsAttentionNote', + ); + // Keep mine re-sends the conflict and leaves the old change, however + // often it is chosen. + await notifier.retryRejected(flushImmediately: false); var current = container.read(strategyOpQueueProvider); - expect(current.attentionByEntityKey[key]!.pending.op.opId, delete.opId); - expect(current.needsAttention, isTrue); - // The sync button explains the refusal, not a generic conflict. - expect(current.lastError, lineupEndInUseMessage); - expect(isSpecificAttentionReason(current.lastError!), isTrue); - final durable = store.load().records.single; + expect(current.queuedByEntityKey.keys, [elementKey]); + expect(current.attentionByEntityKey.keys, [retiredKey]); + expect(current.lastError, retiredLineupOpMessage); + await notifier.retryRejected(flushImmediately: false); + current = container.read(strategyOpQueueProvider); + expect(current.queuedByEntityKey, isNot(contains(retiredKey))); + // Alone in attention it still says why, not that no revision matched. + expect(current.lastError, retiredLineupOpMessage); + expect( + current.attentionByEntityKey[retiredKey]!.pending.op.opId, + retired.opId, + ); + final durable = store.load().records.singleWhere( + (record) => record.entityKey == retiredKey, + ); expect(durable.status, DurableOutboxStatus.attention); + expect(durable.pending.op.opId, retired.opId); + expect(durable.lastError, retiredLineupOpMessage); expect( - friendlyCloudSyncError(durable.lastError!), - contains('still uses this origin or landing spot'), + repository.calls.expand((ops) => ops).map((op) => op.opId), + isNot(contains(retired.opId)), ); - // Keep mine asks again: the delete lands once that lineup is gone. - await notifier.retryRejected(flushImmediately: false); + final discarded = await notifier.discardRejected({retiredKey}); + expect(discarded, {retiredKey}); current = container.read(strategyOpQueueProvider); - final retried = current.queuedByEntityKey[key]!.pending.op; - expect(retried, isA()); - expect(retried.opId, isNot(delete.opId)); - expect(retried.expectedRevision, delete.expectedRevision); + expect(current.attentionByEntityKey, isEmpty); + expect(current.lastError, isNull); + expect( + store.load().records.map((record) => record.entityKey), + isNot(contains(retiredKey)), + ); }); test('an oversized op is durably parked while independent work lands', @@ -2730,26 +2561,22 @@ void main() { ..setActiveStrategy('strategy-1', accountId: 'account-a'); const key = EntitySyncKey.lineup('page-1', 'lineup-1'); - await notifier.enqueue(const LineupAddOp( + final successor = _lineupAdd( + opId: 'lineup-add-successor', + id: 'lineup-1', + name: 'second', + ); + + await notifier.enqueue(_lineupAdd( opId: 'lineup-add-in-flight', - lineupPublicId: 'lineup-1', - pagePublicId: 'page-1', - payload: {'value': 'first'}, - sortIndex: 0, + id: 'lineup-1', + name: 'first', )); final firstFlush = notifier.flushNow(); await repository.firstStarted.future; await notifier.syncDesiredOpsForPage( pageId: 'page-1', - desiredOpsByEntityKey: { - key: const LineupAddOp( - opId: 'lineup-add-successor', - lineupPublicId: 'lineup-1', - pagePublicId: 'page-1', - payload: {'value': 'second'}, - sortIndex: 0, - ), - }, + desiredOpsByEntityKey: {key: successor}, ); repository.completeFirst(const AppliedOpAck( @@ -2760,7 +2587,8 @@ void main() { await repository.secondStarted.future; final finalEdit = repository.calls[1].single as LineupPatchOp; - expect(finalEdit.payload, {'value': 'second'}); + expect(finalEdit.lineupPublicId, 'lineup-1'); + expect(finalEdit.payload, successor.payload); expect(finalEdit.expectedLineupRevision, 1); repository.completeSecond(AppliedOpAck( opId: finalEdit.opId, @@ -2900,12 +2728,10 @@ void main() { ..setActiveStrategy('strategy-1', accountId: 'account-a'); const key = EntitySyncKey.lineup('page-1', 'lineup-1'); - await notifier.enqueue(const LineupAddOp( + await notifier.enqueue(_lineupAdd( opId: 'lineup-add-in-flight', - lineupPublicId: 'lineup-1', - pagePublicId: 'page-1', - payload: {'value': 'first'}, - sortIndex: 0, + id: 'lineup-1', + name: 'first', )); final firstFlush = notifier.flushNow(); await repository.firstStarted.future; @@ -2913,12 +2739,10 @@ void main() { await notifier.syncDesiredOpsForPage( pageId: 'page-1', desiredOpsByEntityKey: { - key: const LineupAddOp( + key: _lineupAdd( opId: 'lineup-add-successor', - lineupPublicId: 'lineup-1', - pagePublicId: 'page-1', - payload: {'value': 'second'}, - sortIndex: 0, + id: 'lineup-1', + name: 'second', ), }, ); @@ -3182,6 +3006,63 @@ ElementPatchOp _cloudElementOp() { ); } +/// An add of lineup [id] as one row, the shape this build sends. +LineupAddOp _lineupAdd({ + required String opId, + String id = 'k', + String pageId = 'page-1', + String name = 'lineup', +}) { + return LineupAddOp( + opId: opId, + lineupPublicId: id, + pagePublicId: pageId, + payload: cloudLineupPayload({ + 'id': id, + 'name': name, + 'origin': {'id': 'o', 'agent': {}}, + 'landing': {'id': 'l', 'ability': {}}, + }), + sortIndex: 0, + ); +} + +/// A link add as builds before one row per lineup wrote it to their outbox: +/// its own row, keyed `lineupLink:`, naming its origin and landing. +LineupAddOp _retiredLinkAdd() { + return const LineupAddOp( + opId: 'old-link-add', + lineupPublicId: 'lineupLink:k', + pagePublicId: 'page-1', + payload: { + 'kind': 'lineupLink', + 'payloadVersion': 1, + 'data': {'id': 'k', 'originId': 'o', 'landingId': 'l'}, + }, + sortIndex: 0, + ); +} + +/// [op] as saved in the durable outbox of account-a's strategy-1. +DurableOutboxRecord _savedRecord( + StrategyOp op, { + DurableOutboxStatus status = DurableOutboxStatus.queued, + String? lastError, + int? latestServerRevision, +}) { + return DurableOutboxRecord( + accountId: 'account-a', + strategyPublicId: 'strategy-1', + entityKey: EntitySyncKey.forStrategyOp(op)!, + pending: PendingOp(op: op, clientId: 'client-a'), + status: status, + createdAt: DateTime(2026), + updatedAt: DateTime(2026), + lastError: lastError, + latestServerRevision: latestServerRevision, + ); +} + PagePatchOp _pageSideOp({ required String opId, required bool isAttack, @@ -3305,16 +3186,10 @@ class _RecordingAckRepository extends ConvexStrategyRepository { } } -/// Refuses every op as the server refuses a link whose origin or landing is -/// gone, or with another lineup refusal. -class _MissingLinkEndRepository extends ConvexStrategyRepository { - _MissingLinkEndRepository({ - this.code = 'LINEUP_LINK_END_MISSING', - this.message = lineupLinkEndMissingMessage, - }) : super(IcarusConvexApi(_UnusedTransport())); - - final String code; - final String message; +/// Refuses every op as the server refuses a lineup whose row lives on +/// another page. +class _FailingLineupRepository extends ConvexStrategyRepository { + _FailingLineupRepository() : super(IcarusConvexApi(_UnusedTransport())); @override Future> applyBatch({ @@ -3327,9 +3202,9 @@ class _MissingLinkEndRepository extends ConvexStrategyRepository { for (final op in ops) FailedOpAck( opId: op.opId, - code: code, - rawCode: code, - message: message, + code: 'LINEUP_PAGE_MISMATCH', + rawCode: 'LINEUP_PAGE_MISMATCH', + message: lineupPageMismatchMessage, ), ]; } diff --git a/test/strategy_page_session_provider_test.dart b/test/strategy_page_session_provider_test.dart index 4411014e..11cd1434 100644 --- a/test/strategy_page_session_provider_test.dart +++ b/test/strategy_page_session_provider_test.dart @@ -535,11 +535,15 @@ Map _agentJson(String id, Offset position) => { 'lineUpID': id, }; -Map _abilityJson(String id) => { +Map _abilityJson( + String id, [ + Offset position = const Offset(30, 40), +]) => + { 'id': 'ability-$id', 'isDeleted': false, 'data': {'type': 'sova', 'index': 2.0}, - 'position': {'dx': 30, 'dy': 40}, + 'position': {'dx': position.dx, 'dy': position.dy}, 'isAlly': true, 'rotation': 0, 'length': 0, @@ -553,27 +557,55 @@ Map _abilityJson(String id) => { 'armLengthsMeters': [10, 10, 10, 10], }; +/// Origin [id] as a lineup row carries it. +Map _originJson( + String id, [ + Offset position = const Offset(10, 20), +]) => + {'id': id, 'agent': _agentJson(id, position)}; + +/// Landing [id] as a lineup row carries it. +Map _landingJson( + String id, [ + Offset position = const Offset(30, 40), +]) => + {'id': id, 'ability': _abilityJson(id, position)}; + +/// One lineup as the server stores it: one row, the link [id] with its own +/// copies of its [origin] and [landing]. RemoteLineup _lineupRow( String pageId, - String kind, - Map data, { + String id, { + required Map origin, + required Map landing, + String name = '', + String notes = 'remote lineup', int revision = 1, int sortIndex = 0, + bool deleted = false, }) { return RemoteLineup( - publicId: cloudLineupRowId(kind, data['id'] as String), + publicId: id, strategyPublicId: 'cloud-strategy', pagePublicId: pageId, - payload: cloudLineupPayload(kind: kind, data: data), + payload: cloudLineupPayload({ + 'id': id, + 'name': name, + 'youtubeLink': '', + 'notes': notes, + 'images': [], + 'origin': origin, + 'landing': landing, + }), sortIndex: sortIndex, revision: revision, - deleted: false, + deleted: deleted, ); } -/// One whole lineup as the server stores it: the origin [id], the landing -/// `landing-[id]` and the link `link-[id]` between them. -List _lineupRows( +/// A lineup on spots of its own: the link `link-[id]` from the origin [id] +/// to the landing `landing-[id]`. +RemoteLineup _lineup( String pageId, String id, { Offset agentPosition = const Offset(10, 20), @@ -581,51 +613,40 @@ List _lineupRows( int revision = 1, int sortIndex = 0, }) { - final landingId = 'landing-$id'; - return [ - _lineupRow( - pageId, - CloudLineupKind.origin, - {'id': id, 'agent': _agentJson(id, agentPosition)}, - revision: revision, - sortIndex: sortIndex, - ), - _lineupRow( - pageId, - CloudLineupKind.landing, - {'id': landingId, 'ability': _abilityJson(landingId)}, - revision: revision, - sortIndex: sortIndex + 1, - ), - _lineupRow( - pageId, - CloudLineupKind.link, - { - 'id': 'link-$id', - 'originId': id, - 'landingId': landingId, - 'name': linkName, - 'youtubeLink': '', - 'notes': 'remote lineup', - 'images': [], - }, - revision: revision, - sortIndex: sortIndex + 2, - ), - ]; + return _lineupRow( + pageId, + 'link-$id', + origin: _originJson(id, agentPosition), + landing: _landingJson('landing-$id'), + name: linkName, + revision: revision, + sortIndex: sortIndex, + ); } -EntitySyncKey _originKey(String pageId, String id) => - EntitySyncKey.lineup(pageId, cloudLineupRowId(CloudLineupKind.origin, id)); +/// The key of the row [_lineup] builds for [id]. +EntitySyncKey _lineupKey(String pageId, String id) => + EntitySyncKey.lineup(pageId, 'link-$id'); + +/// [row] as a teammate left it: its data changed by [edit], one revision on. +RemoteLineup _editedLineup( + RemoteLineup row, + void Function(Map data) edit, { + bool deleted = false, +}) { + final payload = jsonDecode(jsonEncode(row.payload)) as CloudPayload; + edit(payload['data'] as Map); + return RemoteLineup( + publicId: row.publicId, + strategyPublicId: row.strategyPublicId, + pagePublicId: row.pagePublicId, + payload: payload, + sortIndex: row.sortIndex, + revision: row.revision + 1, + deleted: deleted, + ); +} -/// Every row key of the lineup [_lineupRows] builds for [id]. -Set _lineupRowKeys(String pageId, String id) => { - _originKey(pageId, id), - EntitySyncKey.lineup( - pageId, cloudLineupRowId(CloudLineupKind.landing, 'landing-$id')), - EntitySyncKey.lineup( - pageId, cloudLineupRowId(CloudLineupKind.link, 'link-$id')), - }; RemoteEditorSnapshot _editorSnapshot({ required List pages, required RemotePageSnapshot activePage, @@ -2391,13 +2412,13 @@ void main() { expect(container.read(strategySaveStateProvider).isDirty, isFalse); }); - test('a held lineup origin a teammate made undrawable keeps its base', + test('an edit to a held lineup is checked against the version the user saw', () async { final page = _page('page-1', 0); - final rows = _lineupRows(page.publicId, 'a'); + final row = _lineup(page.publicId, 'a'); final remote = _FakeRemoteEditorNotifier(_editorSnapshot( pages: [page], - activePage: _pageSnapshot(page, text: 'before', lineups: rows), + activePage: _pageSnapshot(page, text: 'before', lineups: [row]), )); final container = await _cloudContainer( remote: remote, @@ -2413,27 +2434,17 @@ void main() { container.read(editorPointersProvider.notifier) ..holdEntity(1, 'a') ..down(1); - // The teammate's landing deletion lands first; the origin and link are - // still live but can no longer be drawn. - final landing = rows[1]; + // A teammate's notes edit to the same lineup lands mid-drag. remote.setSnapshot(_editorSnapshot( pages: [page], activePage: _pageSnapshot(page, text: 'after', contentRevision: 2, lineups: [ - rows[0], - RemoteLineup( - publicId: landing.publicId, - strategyPublicId: landing.strategyPublicId, - pagePublicId: landing.pagePublicId, - payload: landing.payload, - sortIndex: landing.sortIndex, - revision: 2, - deleted: true, - ), - rows[2], + _editedLineup(row, (data) => data['notes'] = 'teammate notes'), ]), )); await _settle(); + expect(container.read(textProvider).single.text, 'after'); + expect(container.read(lineUpProvider).links.single.notes, 'remote lineup'); container .read(lineUpProvider.notifier) .updateOriginAgentPosition('a', const Offset(400, 400)); @@ -2441,10 +2452,11 @@ void main() { .read(activePageLiveSyncProvider.notifier) .syncLocalPage( strategyPublicId: 'cloud-strategy', pageId: page.publicId)!; - final originOp = ops[EntitySyncKey.lineup(page.publicId, rows[0].publicId)]; - // A patch against the version the user saw, not a re-add. - expect(originOp, isA()); - expect(originOp!.expectedRevision, 1); + final op = ops[_lineupKey(page.publicId, 'a')]; + // Revision 1, not the teammate's 2: the server refuses it as a conflict + // instead of the move silently overwriting their notes. + expect(op, isA()); + expect(op!.expectedRevision, 1); await _settle(); }); @@ -3198,45 +3210,130 @@ void main() { }); }); - test('a lineup origin placement starts from holds back lineup changes', - () async { - final page = _page('page-1', 0); - final remote = _FakeRemoteEditorNotifier(_editorSnapshot( - pages: [page], - activePage: _pageSnapshot(page, - text: 'before', lineups: _lineupRows(page.publicId, 'a')), - )); - final container = await _cloudContainer( - remote: remote, - queue: _FakeStrategyOpQueueNotifier(), - ); - await container - .read(strategyPageSessionProvider.notifier) - .initializeForStrategy( - strategyId: 'cloud-strategy', - source: StrategySource.cloud, - selectFirstPageIfNeeded: true, - ); - container.read(lineUpProvider.notifier).startFromOrigin('a'); - remote.setSnapshot(_editorSnapshot( - pages: [page], - activePage: _pageSnapshot(page, - text: 'after', - contentRevision: 2, - lineups: _lineupRows(page.publicId, 'a', - agentPosition: const Offset(500, 500), revision: 2)), - )); - await _settle(); - expect(container.read(textProvider).single.text, 'after'); - expect(container.read(lineUpProvider).origins.single.agent.position, - const Offset(10, 20)); - expect(container.read(lineUpProvider).placement?.pinnedOriginId, 'a'); + group('holding a lineup spot while a teammate edits the page', () { + const moved = Offset(500, 500); - container.read(lineUpProvider.notifier).clearPlacement(); - await _settle(); - expect(container.read(lineUpProvider).origins.single.agent.position, - const Offset(500, 500)); - expect(container.read(strategyOpQueueProvider).pending, isEmpty); + /// Lineup `a` from origin `a`, lineup `a2` from the same origin, and + /// lineup `b` on spots of its own, each with its origin at [at]. + List lineups( + RemotePage page, { + Offset at = const Offset(10, 20), + int revision = 1, + String notes = 'remote lineup', + }) => + [ + _lineupRow(page.publicId, 'link-a', + origin: _originJson('a', at), + landing: _landingJson('landing-a'), + notes: notes, + revision: revision), + _lineupRow(page.publicId, 'link-a2', + origin: _originJson('a', at), + landing: _landingJson('landing-a2', const Offset(60, 60)), + notes: notes, + revision: revision, + sortIndex: 1), + _lineupRow(page.publicId, 'link-b', + origin: _originJson('b', at), + landing: _landingJson('landing-b'), + notes: notes, + revision: revision, + sortIndex: 2), + ]; + + Future<(ProviderContainer, _FakeRemoteEditorNotifier, RemotePage)> + open() async { + final page = _page('page-1', 0); + final remote = _FakeRemoteEditorNotifier(_editorSnapshot( + pages: [page], + activePage: _pageSnapshot(page, text: 'before', lineups: lineups(page)), + )); + final container = await _cloudContainer( + remote: remote, + queue: _FakeStrategyOpQueueNotifier(), + ); + await container + .read(strategyPageSessionProvider.notifier) + .initializeForStrategy( + strategyId: 'cloud-strategy', + source: StrategySource.cloud, + selectFirstPageIfNeeded: true, + ); + return (container, remote, page); + } + + Offset originAt(ProviderContainer container, String id) => + container.read(lineUpProvider).originById(id)!.agent.position; + + test('dragging a spot holds only the lineups on it until release', + () async { + final (container, remote, page) = await open(); + container.read(editorPointersProvider.notifier) + ..holdEntity(1, 'landing-a') + ..down(1); + + // A teammate moves every origin, edits every lineup's notes, and adds + // a lineup to the held lineup's origin. + remote.setSnapshot(_editorSnapshot( + pages: [page], + activePage: + _pageSnapshot(page, text: 'after', contentRevision: 2, lineups: [ + ...lineups(page, at: moved, revision: 2, notes: 'teammate notes'), + _lineupRow(page.publicId, 'link-c', + origin: _originJson('a', moved), + landing: _landingJson('landing-c', const Offset(90, 90)), + sortIndex: 3), + ]), + )); + await _settle(); + + // The dragged landing's lineup waits, and so does every lineup that + // shares a spot with it: their shared origin is drawn once. + final lineUps = container.read(lineUpProvider); + expect(container.read(textProvider).single.text, 'after'); + expect(originAt(container, 'a'), const Offset(10, 20)); + expect(lineUps.linkById('link-a')!.notes, 'remote lineup'); + expect(lineUps.linkById('link-a2')!.notes, 'remote lineup'); + expect(lineUps.linkById('link-c'), isNull); + // The unrelated lineup on the same page takes the change at once. + expect(originAt(container, 'b'), moved); + expect(lineUps.linkById('link-b')!.notes, 'teammate notes'); + + container.read(editorPointersProvider.notifier).release(1); + await _settle(); + + final released = container.read(lineUpProvider); + expect(originAt(container, 'a'), moved); + expect(released.linkById('link-a')!.notes, 'teammate notes'); + expect(released.linkById('link-a2')!.notes, 'teammate notes'); + expect(released.links.map((link) => link.id), + ['link-a', 'link-a2', 'link-b', 'link-c']); + expect(released.origins, hasLength(2)); + expect(container.read(strategyOpQueueProvider).pending, isEmpty); + }); + + test('a placement pinned to an origin holds only that origin\'s lineups', + () async { + final (container, remote, page) = await open(); + container.read(lineUpProvider.notifier).startFromOrigin('a'); + remote.setSnapshot(_editorSnapshot( + pages: [page], + activePage: _pageSnapshot(page, + text: 'after', + contentRevision: 2, + lineups: lineups(page, at: moved, revision: 2)), + )); + await _settle(); + expect(container.read(textProvider).single.text, 'after'); + expect(originAt(container, 'a'), const Offset(10, 20)); + expect(originAt(container, 'b'), moved); + expect(container.read(lineUpProvider).placement?.pinnedOriginId, 'a'); + + container.read(lineUpProvider.notifier).clearPlacement(); + await _settle(); + expect(originAt(container, 'a'), moved); + expect(container.read(strategyOpQueueProvider).pending, isEmpty); + }); }); for (final startDuringLoad in [false, true]) { @@ -3635,13 +3732,13 @@ void main() { .map((pending) => pending.op) .firstWhere((op) => op.entityPublicId == textId); final malformedLineup = RemoteLineup( - publicId: 'lineupLink:bad-lineup', + publicId: 'bad-lineup', strategyPublicId: 'cloud-strategy', pagePublicId: page.publicId, payload: const { - 'kind': 'lineupLink', + 'kind': 'lineup', 'payloadVersion': 1, - 'data': {'broken': true}, + 'data': {'id': 'bad-lineup', 'broken': true}, }, sortIndex: 0, revision: 1, @@ -4290,7 +4387,7 @@ void main() { () async { final page = _page('page-1', 0); final container = - await openCloudPage(page, _lineupRows(page.publicId, 'lineup-1')); + await openCloudPage(page, [_lineup(page.publicId, 'lineup-1')]); final lineUps = container.read(lineUpProvider); expect(lineUps.origins.single.id, 'lineup-1'); @@ -4301,17 +4398,71 @@ void main() { await _settle(); }); - test('a link that arrived before its landing is neither drawn nor deleted', + test('copies of a shared spot that disagree draw one copy and send nothing', () async { final page = _page('page-1', 0); - final rows = _lineupRows(page.publicId, 'lineup-1'); - // A teammate's origin and link landed; their landing is still in flight. - final container = await openCloudPage(page, [rows[0], rows[2]]); + // Teammates moved the shared origin and the shared landing at the same + // time, so the rows carrying each disagree. + final container = await openCloudPage(page, [ + _lineupRow(page.publicId, 'link-1', + origin: _originJson('origin', const Offset(100, 100)), + landing: _landingJson('landing-1'), + revision: 5), + _lineupRow(page.publicId, 'link-2', + origin: _originJson('origin', const Offset(200, 200)), + landing: _landingJson('landing', const Offset(220, 220)), + revision: 3, + sortIndex: 1), + _lineupRow(page.publicId, 'link-3', + origin: _originJson('origin-3'), + landing: _landingJson('landing', const Offset(330, 330)), + revision: 3, + sortIndex: 2), + ]); + + // The highest revision's copy is drawn; a tie goes to the greatest id. + final lineUps = container.read(lineUpProvider); + expect( + lineUps.links.map((link) => link.id), ['link-1', 'link-2', 'link-3']); + expect(lineUps.origins.map((origin) => origin.id), ['origin', 'origin-3']); + expect( + lineUps.originById('origin')!.agent.position, const Offset(100, 100)); + expect(lineUps.landings.map((landing) => landing.id), + ['landing-1', 'landing']); + expect(lineUps.landingById('landing')!.ability.position, + const Offset(330, 330)); - expect(container.read(lineUpProvider).links, isEmpty); - expect(container.read(lineUpProvider).origins, isEmpty); + // Choosing a copy is not an edit: live sync writes no row for it. expect(lineupOpsAfterTextEdit(container, page), isEmpty); await _settle(); + expect( + container.read(strategyOpQueueProvider).pending.where( + (pending) => pending.op.entityType == StrategyOpEntityType.lineup), + isEmpty, + ); + + // A real edit to a lineup whose copy lost sends the drawn copy. + container + .read(lineUpProvider.notifier) + .updateLink(lineUps.linkById('link-2')!.copyWith(notes: 'jump throw')); + final ops = container + .read(activePageLiveSyncProvider.notifier) + .syncLocalPage( + strategyPublicId: 'cloud-strategy', pageId: page.publicId)!; + final lineupOps = { + for (final entry in ops.entries) + if (entry.key.kind == EntitySyncKeyKind.lineup) entry.key: entry.value, + }; + expect(lineupOps.keys, [EntitySyncKey.lineup(page.publicId, 'link-2')]); + final patch = lineupOps.values.single as LineupPatchOp; + expect(patch.expectedLineupRevision, 3); + final data = cloudPayloadData(patch.payload); + expect(data['notes'], 'jump throw'); + expect(((data['origin'] as Map)['agent'] as Map)['position'], + {'dx': 100.0, 'dy': 100.0}); + expect(((data['landing'] as Map)['ability'] as Map)['position'], + {'dx': 330.0, 'dy': 330.0}); + await _settle(); }); test('a Paranoia saved at the old size opens corrected and sends nothing', @@ -4536,17 +4687,27 @@ void main() { /// The lineup rows the server stores once [ops] apply to [previous]: /// exactly the rows the client sent, JSON round-tripped, in stored - /// order. [teammate] edits each stored row's data (given its payload - /// kind) in the same round, as a teammate's change landing alongside - /// ours would. + /// order. [teammate] edits each stored row's lineup data in the same + /// round, as a teammate's change landing alongside ours would; a row it + /// changes takes this round's [revision]. List apply( Iterable ops, RemotePage page, { required int revision, required List previous, - void Function(String kind, Map data)? teammate, + void Function(Map data)? teammate, }) { final byId = {for (final lineup in previous) lineup.publicId: lineup}; + RemoteLineup stored(String id, CloudPayload payload, int sortIndex) => + RemoteLineup( + publicId: id, + strategyPublicId: 'cloud-strategy', + pagePublicId: page.publicId, + payload: jsonDecode(jsonEncode(payload)) as Map, + sortIndex: sortIndex, + revision: revision, + deleted: false, + ); for (final op in ops) { final (id, payload, sortIndex) = switch (op) { LineupAddOp( @@ -4573,20 +4734,17 @@ void main() { byId.remove(id); continue; } - byId[id] = RemoteLineup( - publicId: id, - strategyPublicId: 'cloud-strategy', - pagePublicId: page.publicId, - payload: jsonDecode(jsonEncode(payload)) as Map, - sortIndex: sortIndex, - revision: revision, - deleted: false, - ); + byId[id] = stored(id, payload, sortIndex); } if (teammate != null) { - for (final lineup in byId.values) { - teammate(lineup.payload['kind'] as String, - lineup.payload['data'] as Map); + for (final lineup in [...byId.values]) { + final payload = + jsonDecode(jsonEncode(lineup.payload)) as Map; + teammate(payload['data'] as Map); + if (jsonEncode(payload) != jsonEncode(lineup.payload)) { + byId[lineup.publicId] = + stored(lineup.publicId, payload, lineup.sortIndex); + } } } return byId.values.toList() @@ -4630,6 +4788,15 @@ void main() { entry.key: entry.value.pending.op, }; + /// The lineup writes in [ops]. + Map lineupOps( + Map ops) => + { + for (final entry in ops.entries) + if (entry.key.kind == EntitySyncKeyKind.lineup) + entry.key: entry.value, + }; + /// One queue update drains [ops] and publishes their acks. void publishAcks(Map ops, int revision) { final acks = [ @@ -4647,23 +4814,19 @@ void main() { ); } - /// Each row of [kind] in [lineups]. - List rowsOf(List lineups, String kind) => [ - for (final row in lineups) - if (row.payload['kind'] == kind) row - ]; + /// The stored row of lineup [id] in [lineups]. + RemoteLineup rowOf(List lineups, String id) => + lineups.singleWhere((row) => row.publicId == id); - EntitySyncKey keyOf(RemotePage page, String kind, String id) => - EntitySyncKey.lineup(page.publicId, cloudLineupRowId(kind, id)); + EntitySyncKey keyOf(RemotePage page, String id) => + EntitySyncKey.lineup(page.publicId, id); - /// The server never holds a lineup row hydration would skip (the graph - /// form of "no empty groups"). + /// Every row the server holds reads back as the lineup it is keyed by. void expectEveryRowDrawn(List lineups) { - final drawn = lineUpGraphFromCloudRows([ - for (final row in lineups) - CloudLineupRow(publicId: row.publicId, payload: row.payload), - ]).drawnRowIds; - expect(drawn, {for (final row in lineups) row.publicId}); + expect( + lineUpGraphFromRemoteLineups(lineups).links.map((link) => link.id), + [for (final row in lineups) row.publicId], + ); } /// The edits' scheduled sync queues ops; one queue update then drains @@ -4677,7 +4840,7 @@ void main() { required List previous, required int revision, required int contentRevision, - void Function(String kind, Map data)? teammate, + void Function(Map data)? teammate, }) async { await _settle(); final pending = queuedOps(); @@ -4691,8 +4854,8 @@ void main() { return lineups; } - /// Only graph rows are written: never a legacy group. - void expectOnlyGraphRows(Map ops) { + /// Every lineup write is one whole lineup row, keyed by its link. + void expectLineupRows(Map ops) { for (final op in ops.values) { final payload = switch (op) { LineupAddOp(:final payload) => payload, @@ -4700,15 +4863,9 @@ void main() { _ => null, }; if (payload == null) continue; - expect( - payload['kind'], - isIn(const [ - CloudLineupKind.origin, - CloudLineupKind.landing, - CloudLineupKind.link, - ]), - reason: '$op', - ); + expect(payload['kind'], cloudLineupPayloadKind, reason: '$op'); + expect(cloudPayloadData(payload)['id'], op.entityPublicId, + reason: '$op'); } } @@ -4728,13 +4885,10 @@ void main() { expect(lineUps.landingById(lineUps.links.single.landingId), isNotNull); expect(lineUps.landings, hasLength(1)); final afterUndo = desiredOps(container, page); - expectOnlyGraphRows(afterUndo); + expectLineupRows(afterUndo); expect(afterUndo.values.whereType(), isEmpty); expect(afterUndo.values.whereType(), hasLength(1)); - expect( - afterUndo.keys.single, - keyOf(page, CloudLineupKind.link, link.id), - ); + expect(afterUndo.keys.single, keyOf(page, link.id)); await _settle(); }); @@ -4776,25 +4930,12 @@ void main() { previous: lineups, revision: 2, contentRevision: 3); // Then a teammate arms the same origin. - final originRow = rowsOf(lineups, CloudLineupKind.origin).single; - final armed = - jsonDecode(jsonEncode(originRow.payload)) as Map; - ((armed['data'] as Map)['agent'] as Map)['weapon'] = 'vandal'; remote.setSnapshot(serverSnapshot( page, [ - for (final row in lineups) - row == originRow - ? RemoteLineup( - publicId: row.publicId, - strategyPublicId: 'cloud-strategy', - pagePublicId: page.publicId, - payload: armed, - sortIndex: row.sortIndex, - revision: 3, - deleted: false, - ) - : row, + _editedLineup(rowOf(lineups, link.id), (data) { + ((data['origin'] as Map)['agent'] as Map)['weapon'] = 'vandal'; + }), ], 4)); await settleSync(container, 4); @@ -4810,8 +4951,8 @@ void main() { expect(origin.agent.weapon, WeaponType.vandal); final patch = desiredOps(container, page).values.whereType().single; - final agent = cloudPayloadData(patch.payload)['agent'] as Map; - expect(agent['weapon'], 'vandal'); + final sent = cloudPayloadData(patch.payload)['origin'] as Map; + expect((sent['agent'] as Map)['weapon'], 'vandal'); await _settle(); }); @@ -4865,11 +5006,38 @@ void main() { return (container.read(lineUpProvider).linkById(a.id)!, b); } + /// Places lineup A, then B from A's origin to a second landing, each + /// link named, the way the editor does. + (LineUpLink, LineUpLink) placeFanOut(ProviderContainer container) { + final a = place(container); + final lineUps = container.read(lineUpProvider.notifier) + ..updateLink(a.copyWith(name: 'Short')) + ..startFromOrigin(a.originId); + lineUps.setDraftAbility(PlacedAbility( + id: 'ability-y', + data: AgentData.agents[AgentType.sova]!.abilities[2], + position: const Offset(450, 450), + )); + final b = lineUps.commitPlacement(name: 'Long')!; + expect(b.originId, a.originId, reason: 'B fans out from A\'s origin'); + return (container.read(lineUpProvider).linkById(a.id)!, b); + } + List<(String, String, String, String)> linkShape(LineUpState state) => [ for (final link in state.links) (link.id, link.originId, link.landingId, link.name), ]; + /// The ids of the spots [lineups]' rows carry, by the lineup carrying + /// them. + Map spotsOf(List lineups) => { + for (final row in lineups) + row.publicId: ( + (cloudPayloadData(row.payload)['origin'] as Map)['id'] as String, + (cloudPayloadData(row.payload)['landing'] as Map)['id'] as String, + ), + }; + test('a fan-in lineup keeps its shared landing, ids and names', () async { final (container, remote, page) = await openEmpty(); final (a, b) = placeFanIn(container); @@ -4879,13 +5047,13 @@ void main() { final lineups = await land(container, remote, page, previous: const [], revision: 1, contentRevision: 2); - expect(rowsOf(lineups, CloudLineupKind.origin), hasLength(2)); - expect( - rowsOf(lineups, CloudLineupKind.landing).single.publicId, - cloudLineupRowId(CloudLineupKind.landing, a.landingId), - ); - expect(rowsOf(lineups, CloudLineupKind.link), hasLength(2)); + // One row per lineup, each carrying its own copy of the landing. + expect(spotsOf(lineups), { + a.id: (a.originId, a.landingId), + b.id: (b.originId, a.landingId), + }); + // Hydration draws the shared landing once. final hydrated = container.read(lineUpProvider); expect(hydrated.origins.map((origin) => origin.id), originIds); expect(hydrated.landings.single.id, a.landingId); @@ -4897,22 +5065,93 @@ void main() { await _settle(); }); - test('undoing a deletion rejoins the surviving shared landing', () async { + test('a fan-out lineup keeps its shared origin, ids and names', () async { + final (container, remote, page) = await openEmpty(); + final (a, b) = placeFanOut(container); + final landingIds = container + .read(lineUpProvider) + .landings + .map((landing) => landing.id) + .toList(); + + final lineups = await land(container, remote, page, + previous: const [], revision: 1, contentRevision: 2); + + expect(spotsOf(lineups), { + a.id: (a.originId, a.landingId), + b.id: (a.originId, b.landingId), + }); + + final hydrated = container.read(lineUpProvider); + expect(hydrated.origins.single.id, a.originId); + expect(hydrated.origins.single.agent.position, placedAt); + expect(hydrated.landings.map((landing) => landing.id), landingIds); + expect(linkShape(hydrated), [ + (a.id, a.originId, a.landingId, 'Short'), + (b.id, a.originId, b.landingId, 'Long'), + ]); + expect(desiredOps(container, page), isEmpty); + await _settle(); + }); + + test('moving a shared spot patches every lineup on it and nothing else', + () async { final (container, remote, page) = await openEmpty(); final (a, b) = placeFanIn(container); + final other = place(container); var lineups = await land(container, remote, page, previous: const [], revision: 1, contentRevision: 2); - container.read(lineUpProvider.notifier).deleteOrigin(a.originId); + // A's row moves on to revision 2; B's and the other's stay at 1. + container.read(lineUpProvider.notifier).updateLink( + container.read(lineUpProvider).linkById(a.id)!.copyWith(notes: 'x')); lineups = await land(container, remote, page, previous: lineups, revision: 2, contentRevision: 3); expect( - lineups.map((row) => row.publicId).toSet(), + {for (final row in lineups) row.publicId: row.revision}, + {a.id: 2, b.id: 1, other.id: 1}, + ); + + const moved = Offset(320, 340); + container + .read(lineUpProvider.notifier) + .updateLandingPosition(a.landingId, moved); + + // One patch per lineup on the landing, each checked against its own + // row's revision, each carrying the moved landing. + final ops = desiredOps(container, page); + expectLineupRows(ops); + expect(ops.keys.toSet(), {keyOf(page, a.id), keyOf(page, b.id)}); + final patches = ops.values.cast(); + expect( { - cloudLineupRowId(CloudLineupKind.landing, a.landingId), - cloudLineupRowId(CloudLineupKind.origin, b.originId), - cloudLineupRowId(CloudLineupKind.link, b.id), + for (final op in patches) op.lineupPublicId: op.expectedLineupRevision }, + {a.id: 2, b.id: 1}, ); + for (final op in patches) { + final landing = cloudPayloadData(op.payload)['landing'] as Map; + expect((landing['ability'] as Map)['position'], + {'dx': moved.dx, 'dy': moved.dy}); + } + + lineups = await land(container, remote, page, + previous: lineups, revision: 3, contentRevision: 4); + final hydrated = container.read(lineUpProvider); + expect(hydrated.landingById(a.landingId)!.ability.position, moved); + expect(hydrated.landings, hasLength(2)); + expect(desiredOps(container, page), isEmpty); + await _settle(); + }); + + test('undoing a deletion rejoins the surviving shared landing', () async { + final (container, remote, page) = await openEmpty(); + final (a, b) = placeFanIn(container); + var lineups = await land(container, remote, page, + previous: const [], revision: 1, contentRevision: 2); + container.read(lineUpProvider.notifier).deleteOrigin(a.originId); + lineups = await land(container, remote, page, + previous: lineups, revision: 2, contentRevision: 3); + expect(spotsOf(lineups), {b.id: (b.originId, a.landingId)}); container.read(actionProvider.notifier).undoAction(); @@ -4923,36 +5162,49 @@ void main() { (a.id, a.originId, a.landingId, 'From heaven'), ]); final ops = desiredOps(container, page); - expectOnlyGraphRows(ops); - expect(ops.keys.toSet(), { - keyOf(page, CloudLineupKind.origin, a.originId), - keyOf(page, CloudLineupKind.link, a.id), - }); - expect(ops.values, everyElement(isA())); - expect( - container.read(activePageLiveSyncProvider).unsyncableLineupKeys, - isEmpty, - ); + expectLineupRows(ops); + expect(ops.keys, [keyOf(page, a.id)]); + expect(ops.values.single, isA()); lineups = await land(container, remote, page, previous: lineups, revision: 3, contentRevision: 4); expect(linkShape(container.read(lineUpProvider)), hasLength(2)); await _settle(); }); - test('deleting one origin of a fan-in writes only its own rows', () async { + test( + 'deleting a lineup takes the spots nothing else uses and keeps a ' + 'shared one', () async { final (container, remote, page) = await openEmpty(); - final (a, _) = placeFanIn(container); - await land(container, remote, page, + final (a, b) = placeFanIn(container); + var lineups = await land(container, remote, page, previous: const [], revision: 1, contentRevision: 2); - container.read(lineUpProvider.notifier).deleteOrigin(a.originId); + container.read(lineUpProvider.notifier).deleteLink(a.id); + // A's origin went with it; the landing B still uses stayed. + var graph = container.read(lineUpProvider).graph; + expect(graph.origins.map((origin) => origin.id), [b.originId]); + expect(graph.landings.map((landing) => landing.id), [a.landingId]); + // Only A's row is deleted; B's row, landing copy and all, is left be. final ops = desiredOps(container, page); - expect(ops.keys.toSet(), { - keyOf(page, CloudLineupKind.origin, a.originId), - keyOf(page, CloudLineupKind.link, a.id), - }); - expect(ops.values, everyElement(isA())); + expect(ops.keys, [keyOf(page, a.id)]); + expect(ops.values.single, isA()); + lineups = await land(container, remote, page, + previous: lineups, revision: 2, contentRevision: 3); + expect(spotsOf(lineups), {b.id: (b.originId, a.landingId)}); + graph = container.read(lineUpProvider).graph; + expect(graph.links.map((link) => link.id), [b.id]); + expect(graph.landings.map((landing) => landing.id), [a.landingId]); + + // Deleting the last lineup on the landing takes the landing too. + container.read(lineUpProvider.notifier).deleteLink(b.id); + graph = container.read(lineUpProvider).graph; + expect(graph.origins, isEmpty); + expect(graph.landings, isEmpty); + lineups = await land(container, remote, page, + previous: lineups, revision: 3, contentRevision: 4); + expect(lineups, isEmpty); + expect(container.read(lineUpProvider).graph.landings, isEmpty); await _settle(); }); @@ -4968,9 +5220,16 @@ void main() { notes: 'jump throw', )); await _settle(); + // Only A's row is written, checked against A's own revision, so a + // teammate's write to B's row cannot conflict with it. + final mine = queuedOps(); expect( - queuedOps().keys.where((key) => key.kind == EntitySyncKeyKind.lineup), - [keyOf(page, CloudLineupKind.link, a.id)], + mine.keys.where((key) => key.kind == EntitySyncKeyKind.lineup), + [keyOf(page, a.id)], + ); + expect( + (mine[keyOf(page, a.id)]! as LineupPatchOp).expectedLineupRevision, + 1, ); lineups = await land( @@ -4980,136 +5239,126 @@ void main() { previous: lineups, revision: 2, contentRevision: 3, - teammate: (kind, data) { - if (kind == CloudLineupKind.link && data['id'] == b.id) { - data['notes'] = 'teammate notes'; - } + teammate: (data) { + if (data['id'] == b.id) data['notes'] = 'teammate notes'; }, ); final hydrated = container.read(lineUpProvider); expect(hydrated.linkById(a.id)!.notes, 'jump throw'); expect(hydrated.linkById(b.id)!.notes, 'teammate notes'); + expect(hydrated.landings.single.id, a.landingId); expect(desiredOps(container, page), isEmpty); + expect(queue.state.attentionByEntityKey, isEmpty); await _settle(); }); - test( - "deleting a lineup whose origin a teammate's new lineup uses keeps " - 'theirs', () async { + test('a stale edit to a lineup a teammate changed conflicts', () async { final (container, remote, page) = await openEmpty(); - final first = place(container); - final landed = await land(container, remote, page, + final link = place(container); + final lineups = await land(container, remote, page, previous: const [], revision: 1, contentRevision: 2); - final originKey = keyOf(page, CloudLineupKind.origin, first.originId); - - // A teammate's lineup from the same origin lands, but this canvas has - // not drawn it yet. - RemoteLineup teammateRow(String kind, Map data) => - RemoteLineup( - publicId: cloudLineupRowId(kind, data['id'] as String), - strategyPublicId: 'cloud-strategy', - pagePublicId: page.publicId, - payload: cloudLineupPayload(kind: kind, data: data), - sortIndex: 10, - revision: 1, - deleted: false, - ); - Map dataOf(String kind) => Map.from( - rowsOf(landed, kind).single.payload['data'] as Map); - final theirLanding = dataOf(CloudLineupKind.landing) - ..['id'] = 'their-landing' - ..['ability'] = { - ...(dataOf(CloudLineupKind.landing)['ability'] as Map), - 'id': 'their-ability', - 'lineUpID': 'their-landing', - }; - final theirLink = dataOf(CloudLineupKind.link) - ..['id'] = 'their-link' - ..['landingId'] = 'their-landing'; - - // The user deletes their lineup, which takes its origin with it. - container.read(lineUpProvider.notifier).deleteLink(first.id); + container + .read(lineUpProvider.notifier) + .updateLink(link.copyWith(notes: 'mine')); await _settle(); - final sent = queuedOps(); - expect(sent[originKey], isA()); - - // The server applies the link and landing deletes and refuses the - // origin's, since the teammate's link still names it. - final onServer = [ - for (final row in landed) - if (row.payload['kind'] == CloudLineupKind.origin) row, - teammateRow(CloudLineupKind.landing, theirLanding), - teammateRow(CloudLineupKind.link, theirLink), - ]; - remote.initialSnapshot = serverSnapshot(page, onServer, 3); - final acks = [ - for (final entry in sent.entries) - AckedEntityIntent( - entityKey: entry.key, - op: entry.value, - ack: entry.key == originKey - ? FailedOpAck( - opId: entry.value.opId, - code: 'LINEUP_END_IN_USE', - rawCode: 'LINEUP_END_IN_USE', - message: lineupEndInUseMessage, - ) - : AppliedOpAck(opId: entry.value.opId, revision: 2), - ), - ]; - queue.state = queue.state.copyWith( - queuedByEntityKey: const {}, - attentionByEntityKey: { - originKey: QueuedEntityIntent( - entityKey: originKey, - pending: PendingOp(op: sent[originKey]!, clientId: 'test-client'), - ), - }, - lastError: 'Some saved work needs attention.', - lastAcks: [for (final intent in acks) intent.ack], - lastAckBatch: acks, - ); - remote.setSnapshot(serverSnapshot(page, onServer, 3)); - for (var i = 0; i < 10; i++) { - await _settle(); - } + final key = keyOf(page, link.id); + final mine = queuedOps()[key]! as LineupPatchOp; + expect(mine.expectedLineupRevision, 1); - // Until the user chooses, their delete still shows, the teammate's - // lineup is not deleted behind their back, and the work needs - // attention. - expect(container.read(lineUpProvider).graph.links, isEmpty); + // A teammate's notes edit to the same lineup lands first. + remote.setSnapshot(serverSnapshot( + page, + [ + _editedLineup( + rowOf(lineups, link.id), (data) => data['notes'] = 'theirs'), + ], + 3, + )); + await _settle(); + // Still checked against the version the user saw: never re-based onto + // theirs, which would overwrite it in silence. expect( - queuedOps().values.whereType(), - isEmpty, + (queuedOps()[key]! as LineupPatchOp).expectedLineupRevision, + 1, ); - expect(queue.state.attentionByEntityKey.keys, [originKey]); - // Using theirs brings back the origin with the teammate's lineup. + // The server refuses it; the user sees their work and a conflict. + queue.reject(queuedOps()[key]!); + await _settle(); + expect(container.read(lineUpProvider).links.single.notes, 'mine'); + expect(queue.state.needsAttention, isTrue); + expect(queue.state.attentionByEntityKey.keys, [key]); + expect(container.read(strategyConflictProvider), hasLength(1)); + + // Using theirs shows the teammate's notes and sends nothing. final resolved = await container .read(strategyPageSessionProvider.notifier) .useCloudVersionsForRejected(); await _settle(); - expect(resolved, isTrue); - final graph = container.read(lineUpProvider).graph; - expect(graph.links.map((link) => link.id), ['their-link']); - expect(graph.origins.map((origin) => origin.id), [first.originId]); + expect(container.read(lineUpProvider).links.single.notes, 'theirs'); expect(queue.state.attentionByEntityKey, isEmpty); expect( queuedOps().keys.where((key) => key.kind == EntitySyncKeyKind.lineup), isEmpty, ); + }); + + test( + 'deleting a spot while a teammate adds a lineup to it keeps ' + 'theirs whole', () async { + final (container, remote, page) = await openEmpty(); + final first = place(container); + final landed = await land(container, remote, page, + previous: const [], revision: 1, contentRevision: 2); + + // A teammate's lineup from the same origin, carrying their own copy + // of it, lands; this canvas has not drawn it yet. + final theirs = RemoteLineup( + publicId: 'their-link', + strategyPublicId: 'cloud-strategy', + pagePublicId: page.publicId, + payload: cloudLineupPayload({ + ...cloudPayloadData(rowOf(landed, first.id).payload), + 'id': 'their-link', + 'name': 'Theirs', + 'landing': _landingJson('their-landing', const Offset(80, 80)), + }), + sortIndex: 10, + revision: 1, + deleted: false, + ); + + // The user deletes the origin, which takes their only lineup on it. + container.read(lineUpProvider.notifier).deleteOrigin(first.originId); + await _settle(); + final sent = queuedOps(); + expect(lineupOps(sent).keys, [keyOf(page, first.id)]); + expect(lineupOps(sent).values.single, isA()); + + // The server deletes the user's row; the teammate's row is untouched. + remote.initialSnapshot = serverSnapshot(page, [theirs], 3); + publishAcks(sent, 2); + await settleSync(container, 3); + + // Their lineup is drawn whole, on the origin from its own row. + final graph = container.read(lineUpProvider).graph; + expect(graph.links.map((link) => link.id), ['their-link']); + expect(graph.origins.map((origin) => origin.id), [first.originId]); + expect(graph.origins.single.agent.position, placedAt); + expect(graph.landings.map((landing) => landing.id), ['their-landing']); + expect(queue.state.attentionByEntityKey, isEmpty); + expect(desiredOps(container, page), isEmpty); await _settle(); }); - test('a lineup placed from an origin a teammate deleted re-adds it', + test('a lineup placed from an origin a teammate deleted saves whole', () async { final (container, remote, page) = await openEmpty(); final first = place(container); final landed = await land(container, remote, page, previous: const [], revision: 1, contentRevision: 2); - final originKey = keyOf(page, CloudLineupKind.origin, first.originId); // The user starts a new lineup from the landed origin... final lineUps = container.read(lineUpProvider.notifier) @@ -5137,30 +5386,22 @@ void main() { 3, )); await _settle(); - // Nothing the user has made names the origin yet, so the teammate's - // delete stands. - expect(desiredOps(container, page), isNot(contains(originKey))); + // The teammate's delete stands: the user never re-sends it. + expect( + desiredOps(container, page), isNot(contains(keyOf(page, first.id)))); final second = lineUps.commitPlacement()!; await _settle(); - // The origin goes back with the new landing and link, restoring the - // teammate's tombstone (revision 2) rather than orphaning the link. - final queued = queuedOps(); + // The new lineup's row carries its own copy of the origin, so it + // saves whole without the deleted lineup. + final queued = lineupOps(queuedOps()); + expect(queued.keys, [keyOf(page, second.id)]); + final add = queued.values.single as LineupAddOp; expect( - queued[originKey], - isA() - .having((op) => op.expectedLineupRevision, 'revision', 2), + (cloudPayloadData(add.payload)['origin'] as Map)['id'], + first.originId, ); - expect( - queued.keys, - containsAll([ - keyOf(page, CloudLineupKind.landing, second.landingId), - keyOf(page, CloudLineupKind.link, second.id), - ]), - ); - expect( - queued, isNot(contains(keyOf(page, CloudLineupKind.link, first.id)))); // The server holds the new lineup whole, and it loads back. await land(container, remote, page, @@ -5171,7 +5412,7 @@ void main() { await _settle(); }); - test('each lineup action undoes after hydration, one row at a time', + test('each lineup action undoes after hydration, one write at a time', () async { final (container, remote, page) = await openEmpty(); final link = place(container); @@ -5209,22 +5450,22 @@ void main() { await landNext(); expect(state().linkById(link.id)!.name, 'Heaven'); - final origin = keyOf(page, CloudLineupKind.origin, link.originId); - final landing = keyOf(page, CloudLineupKind.landing, link.landingId); - final linkKey = keyOf(page, CloudLineupKind.link, link.id); + final key = keyOf(page, link.id); final history = container.read(actionProvider.notifier); - Future> undoAndLand() async { + // Undoes one step and lands it; returns the one write it made. + Future undoAndLand() async { history.undoAction(); final ops = desiredOps(container, page); - expectOnlyGraphRows(ops); + expectLineupRows(ops); + expect(ops.keys, [key]); await landNext(); - return ops.keys.toSet(); + return ops.values.single; } - expect(await undoAndLand(), {origin}); + expect(await undoAndLand(), isA()); expect(state().originById(link.originId)!.agent.weapon, isNot(WeaponType.vandal)); - expect(await undoAndLand(), {landing}); + expect(await undoAndLand(), isA()); expect( state() .landingById(link.landingId)! @@ -5233,14 +5474,14 @@ void main() { .showRangeOutline, isTrue, ); - expect(await undoAndLand(), {landing}); + expect(await undoAndLand(), isA()); expect(state().landingById(link.landingId)!.ability.position, const Offset(300, 300)); - expect(await undoAndLand(), {origin}); + expect(await undoAndLand(), isA()); expect(state().originById(link.originId)!.agent.position, placedAt); - expect(await undoAndLand(), {linkKey}); + expect(await undoAndLand(), isA()); expect(state().linkById(link.id)!.name, ''); - expect(await undoAndLand(), {origin, landing, linkKey}); + expect(await undoAndLand(), isA()); expect(state().links, isEmpty); expect(lineups, isEmpty); @@ -5251,44 +5492,6 @@ void main() { await _settle(); }); - test('a lineup whose landing is missing is refused, loudly', () async { - final (container, remote, page) = await openEmpty(); - final link = place(container); - await land(container, remote, page, - previous: const [], revision: 1, contentRevision: 2); - final graph = container.read(lineUpProvider).graph; - // A graph whose link names a landing the canvas no longer has. - container.read(lineUpProvider.notifier).fromHive( - LineUpGraph(origins: graph.origins, links: graph.links), - ); - - final ops = desiredOps(container, page); - - // Neither half-written nor half-deleted: the origin and link are not - // sent, and the landing row they need is not deleted. - final held = { - keyOf(page, CloudLineupKind.origin, link.originId), - keyOf(page, CloudLineupKind.landing, link.landingId), - keyOf(page, CloudLineupKind.link, link.id), - }; - expect(ops.keys.where(held.contains), isEmpty); - expect(ops.values.whereType(), isEmpty); - // Drives the attention status (see cloud_sync_button_test). - expect( - container.read(activePageLiveSyncProvider).unsyncableLineupKeys, - held, - ); - - // Restoring the landing clears the refusal. - container.read(lineUpProvider.notifier).fromHive(graph); - desiredOps(container, page); - expect( - container.read(activePageLiveSyncProvider).unsyncableLineupKeys, - isEmpty, - ); - await _settle(); - }); - test('undoing a notes edit keeps an image a teammate added', () async { final (container, remote, page) = await openEmpty(); final link = place(container); @@ -5304,8 +5507,7 @@ void main() { previous: lineups, revision: 2, contentRevision: 3, - teammate: (kind, data) { - if (kind != CloudLineupKind.link) return; + teammate: (data) { data['images'] = [ {'id': 'teammate-image', 'fileExtension': '.png'}, ]; @@ -5324,8 +5526,7 @@ void main() { final patch = desiredOps(container, page).values.whereType().single; final item = cloudPayloadData(patch.payload); - expect(patch.lineupPublicId, - cloudLineupRowId(CloudLineupKind.link, link.id)); + expect(patch.lineupPublicId, link.id); expect(item['notes'], ''); expect( (item['images'] as List).map((image) => (image as Map)['id']), @@ -5410,8 +5611,7 @@ void main() { previous: lineups, revision: 2, contentRevision: 3, - teammate: (kind, data) { - if (kind != CloudLineupKind.link) return; + teammate: (data) { (data['images'] as List) .insert(1, {'id': 'x', 'fileExtension': '.png'}); }, @@ -5446,10 +5646,9 @@ void main() { previous: lineups, revision: 2, contentRevision: 3, - teammate: (kind, data) { - if (kind != CloudLineupKind.landing) return; - ((data['ability'] as Map)['visualState'] as Map)['showRangeFill'] = - false; + teammate: (data) { + final ability = (data['landing'] as Map)['ability'] as Map; + (ability['visualState'] as Map)['showRangeFill'] = false; }, ); @@ -5483,9 +5682,8 @@ void main() { previous: lineups, revision: 2, contentRevision: 3, - teammate: (kind, data) { - if (kind != CloudLineupKind.origin) return; - (data['agent'] as Map)['weapon'] = 'vandal'; + teammate: (data) { + ((data['origin'] as Map)['agent'] as Map)['weapon'] = 'vandal'; }, ); @@ -5493,7 +5691,7 @@ void main() { expect(origin.agent.position, moved); expect(origin.agent.weapon, WeaponType.vandal); final ops = desiredOps(container, page); - expect(ops[keyOf(page, CloudLineupKind.origin, link.originId)], isNull); + expect(ops[keyOf(page, link.id)], isNull); await _settle(); }); @@ -5547,9 +5745,8 @@ void main() { first, revision: 2, previous: lineups, - teammate: (kind, data) { - if (kind != CloudLineupKind.origin) return; - (data['agent'] as Map)['weapon'] = 'vandal'; + teammate: (data) { + ((data['origin'] as Map)['agent'] as Map)['weapon'] = 'vandal'; }, ); remote.pageCatalog[first.publicId] = _pageSnapshot( @@ -5572,8 +5769,7 @@ void main() { expect(origin.agent.position, moved); expect(origin.agent.weapon, WeaponType.vandal); expect( - desiredOps(container, first)[ - keyOf(first, CloudLineupKind.origin, link.originId)], + desiredOps(container, first)[keyOf(first, link.id)], isNull, ); await _settle(); @@ -5588,7 +5784,7 @@ void main() { pages: [page], activePage: _pageSnapshot( page, - lineups: _lineupRows(page.publicId, 'lineup-a'), + lineups: [_lineup(page.publicId, 'lineup-a')], ), )); final container = await _cloudContainer( @@ -5652,9 +5848,8 @@ void main() { .updateOriginAgentPosition('lineup-a', moved); // The move lands and a teammate's lineup B arrives with it. await ackAndRehydrate(container, remote, page, [ - ..._lineupRows(page.publicId, 'lineup-a', - agentPosition: moved, revision: 2), - ..._lineupRows(page.publicId, 'lineup-b', sortIndex: 3), + _lineup(page.publicId, 'lineup-a', agentPosition: moved, revision: 2), + _lineup(page.publicId, 'lineup-b', sortIndex: 1), ]); expect( container.read(lineUpProvider).origins.map((origin) => origin.id), @@ -5680,7 +5875,7 @@ void main() { expect(desired, isNotNull); expect(desired!.values.whereType(), isEmpty); expect( - desired[_originKey(page.publicId, 'lineup-a')], + desired[_lineupKey(page.publicId, 'lineup-a')], isA(), ); expect( @@ -5699,7 +5894,7 @@ void main() { pages: [page], activePage: _pageSnapshot( page, - lineups: _lineupRows(page.publicId, 'lineup-1'), + lineups: [_lineup(page.publicId, 'lineup-1')], ), )), queue: _FakeStrategyOpQueueNotifier(), @@ -5744,7 +5939,7 @@ void main() { page, text: 'remote', contentRevision: 2, - lineups: _lineupRows(page.publicId, 'lineup-1'), + lineups: [_lineup(page.publicId, 'lineup-1')], ), )); @@ -6715,31 +6910,15 @@ void main() { expect(container.read(textProvider), isEmpty); }); - /// [lineup]'s rows as a teammate left them with [notes] on its link. - List withNotes(List lineup, String notes) { - return [ - for (final row in lineup) - if (row.payload['kind'] != CloudLineupKind.link) - row - else - RemoteLineup( - publicId: row.publicId, - strategyPublicId: row.strategyPublicId, - pagePublicId: row.pagePublicId, - payload: (jsonDecode(jsonEncode(row.payload)) as CloudPayload) - ..['data']['notes'] = notes, - sortIndex: row.sortIndex, - revision: row.revision + 1, - deleted: false, - ), - ]; - } + /// [lineup]'s row as a teammate left it, with [notes]. + List withNotes(RemoteLineup lineup, String notes) => + [_editedLineup(lineup, (data) => data['notes'] = notes)]; test( 'redoing a lineup clear a teammate already undid by deleting ' 'does not resurrect it on the next undo', () async { - final mine = _lineupRows('page-1', 'mine'); - final (container, remote, page) = await open(const [], lineups: mine); + final mine = _lineup('page-1', 'mine'); + final (container, remote, page) = await open(const [], lineups: [mine]); history(container).clearGroupAsAction(ActionGroup.lineUp); history(container).undoAction(); @@ -6753,15 +6932,15 @@ void main() { expect( desiredOps(container, page) .keys - .where(_lineupRowKeys(page.publicId, 'mine').contains), + .where((key) => key == _lineupKey(page.publicId, 'mine')), isEmpty, ); }); test('a lineup clear redone and undone keeps a teammate notes edit', () async { - final mine = _lineupRows('page-1', 'mine'); - final (container, remote, page) = await open(const [], lineups: mine); + final mine = _lineup('page-1', 'mine'); + final (container, remote, page) = await open(const [], lineups: [mine]); history(container).clearGroupAsAction(ActionGroup.lineUp); history(container).undoAction(); @@ -6782,8 +6961,8 @@ void main() { test( 'undoing a lineup clear leaves a lineup a teammate restored, ' 'and redo does not remove it', () async { - final mine = _lineupRows('page-1', 'mine'); - final (container, remote, page) = await open(const [], lineups: mine); + final mine = _lineup('page-1', 'mine'); + final (container, remote, page) = await open(const [], lineups: [mine]); history(container).clearGroupAsAction(ActionGroup.lineUp); await land(container, remote, page, lineups: withNotes(mine, 'restored')); @@ -6802,8 +6981,8 @@ void main() { test( 'redoing a lineup deletion a teammate already made removes nothing ' 'and the next undo does not resurrect it', () async { - final mine = _lineupRows('page-1', 'mine'); - final (container, remote, page) = await open(const [], lineups: mine); + final mine = _lineup('page-1', 'mine'); + final (container, remote, page) = await open(const [], lineups: [mine]); container.read(lineUpProvider.notifier).deleteOrigin('mine'); history(container).undoAction(); @@ -6818,7 +6997,7 @@ void main() { expect( desiredOps(container, page) .keys - .where(_lineupRowKeys(page.publicId, 'mine').contains), + .where((key) => key == _lineupKey(page.publicId, 'mine')), isEmpty, ); }); @@ -6861,8 +7040,8 @@ void main() { }); test('a lineup weapon change a teammate deleted leaves history', () async { - final mine = _lineupRows('page-1', 'mine'); - final (container, remote, page) = await open(const [], lineups: mine); + final mine = _lineup('page-1', 'mine'); + final (container, remote, page) = await open(const [], lineups: [mine]); container .read(lineUpProvider.notifier) @@ -6874,8 +7053,8 @@ void main() { test('undoing a lineup weapon change a teammate deleted does nothing', () async { - final mine = _lineupRows('page-1', 'mine'); - final (container, remote, page) = await open(const [], lineups: mine); + final mine = _lineup('page-1', 'mine'); + final (container, remote, page) = await open(const [], lineups: [mine]); container .read(lineUpProvider.notifier) @@ -6891,8 +7070,8 @@ void main() { test( 'undoing a lineup notes edit whose link a teammate deleted does ' 'nothing', () async { - final mine = _lineupRows('page-1', 'mine'); - final (container, remote, page) = await open(const [], lineups: mine); + final mine = _lineup('page-1', 'mine'); + final (container, remote, page) = await open(const [], lineups: [mine]); final lineUps = container.read(lineUpProvider.notifier); lineUps.updateLink( @@ -6912,32 +7091,6 @@ void main() { expect(container.read(lineUpProvider).links, isEmpty); }); - /// [lineup]'s rows with the link's data changed by [edit], as a teammate - /// left them. - List editLink( - List lineup, - void Function(Map link) edit, - ) { - RemoteLineup edited(RemoteLineup row) { - final payload = jsonDecode(jsonEncode(row.payload)) as CloudPayload; - edit(payload['data'] as Map); - return RemoteLineup( - publicId: row.publicId, - strategyPublicId: row.strategyPublicId, - pagePublicId: row.pagePublicId, - payload: payload, - sortIndex: row.sortIndex, - revision: row.revision + 1, - deleted: false, - ); - } - - return [ - for (final row in lineup) - row.payload['kind'] == CloudLineupKind.link ? edited(row) : row, - ]; - } - Map image(String id) => {'id': id, 'fileExtension': '.png'}; @@ -6950,11 +7103,11 @@ void main() { test( 'redoing an image removal a teammate already made, then undoing, ' 'does not bring the image back', () async { - final mine = editLink( - _lineupRows('page-1', 'mine'), + final mine = _editedLineup( + _lineup('page-1', 'mine'), (link) => link['images'] = [image('x'), image('y')], ); - final (container, remote, page) = await open(const [], lineups: mine); + final (container, remote, page) = await open(const [], lineups: [mine]); final link = container.read(lineUpProvider).links.single; container.read(lineUpProvider.notifier).updateLink( @@ -6965,8 +7118,9 @@ void main() { history(container).undoAction(); expect(linkImages(container), ['x', 'y']); // A teammate removes x too. - await land(container, remote, page, - lineups: editLink(mine, (link) => link['images'] = [image('y')])); + await land(container, remote, page, lineups: [ + _editedLineup(mine, (link) => link['images'] = [image('y')]), + ]); expect(linkImages(container), ['y']); history(container).redoAction(); @@ -6978,11 +7132,11 @@ void main() { test( 'an edit that removed an image and changed notes replays only the ' 'notes once a teammate removed the image', () async { - final mine = editLink( - _lineupRows('page-1', 'mine'), + final mine = _editedLineup( + _lineup('page-1', 'mine'), (link) => link['images'] = [image('x'), image('y')], ); - final (container, remote, page) = await open(const [], lineups: mine); + final (container, remote, page) = await open(const [], lineups: [mine]); final link = container.read(lineUpProvider).links.single; container.read(lineUpProvider.notifier).updateLink( @@ -6992,8 +7146,9 @@ void main() { ), ); history(container).undoAction(); - await land(container, remote, page, - lineups: editLink(mine, (link) => link['images'] = [image('y')])); + await land(container, remote, page, lineups: [ + _editedLineup(mine, (link) => link['images'] = [image('y')]), + ]); history(container).redoAction(); expect(container.read(lineUpProvider).links.single.notes, 'mine'); @@ -7266,12 +7421,12 @@ void main() { test('undoing a lineup clear keeps a lineup a teammate added', () async { final (container, remote, page) = - await open(const [], lineups: _lineupRows('page-1', 'mine')); + await open(const [], lineups: [_lineup('page-1', 'mine')]); history(container).clearGroupAsAction(ActionGroup.lineUp); expect(container.read(lineUpProvider).origins, isEmpty); await land(container, remote, page, - lineups: _lineupRows(page.publicId, 'theirs')); + lineups: [_lineup(page.publicId, 'theirs')]); history(container).undoAction(); expect( diff --git a/test/unsaved_strategy_guard_test.dart b/test/unsaved_strategy_guard_test.dart index c37000ec..f77e8b51 100644 --- a/test/unsaved_strategy_guard_test.dart +++ b/test/unsaved_strategy_guard_test.dart @@ -594,7 +594,7 @@ void main() { clientId: 'guard-client', attentionByEntityKey: {_guardEntityKey: _guardPendingIntent}, durableLoaded: true, - lastError: lineupLinkEndMissingMessage, + lastError: lineupPageMismatchMessage, ), ); container = ProviderContainer( @@ -618,7 +618,7 @@ void main() { await tester.pumpAndSettle(); expect(find.text('Leave anyway'), findsOneWidget); - expect(find.textContaining('on this page in the cloud'), findsOneWidget); + expect(find.textContaining('another page'), findsOneWidget); expect(find.textContaining('from the sync button'), findsOneWidget); expect(find.textContaining('retry it later'), findsNothing); await tester.tap(find.text('Leave anyway')); diff --git a/test/widgets/cloud_sync_button_test.dart b/test/widgets/cloud_sync_button_test.dart index 605d0810..3e970547 100644 --- a/test/widgets/cloud_sync_button_test.dart +++ b/test/widgets/cloud_sync_button_test.dart @@ -377,20 +377,6 @@ void main() { expect(container.read(cloudSyncStatusProvider), CloudSyncStatus.attention); }); - test('a lineup live sync refused to send shows attention', () async { - final container = _createContainer( - liveSyncState: ActivePageLiveSyncState( - unsyncableLineupKeys: { - const EntitySyncKey.lineup('page-1', 'lineup-1') - }, - ), - ); - addTearDown(container.dispose); - await container.read(convexConnectionProvider.future); - - expect(container.read(cloudSyncStatusProvider), CloudSyncStatus.attention); - }); - testWidgets('an active text draft can never appear synced', (tester) async { final container = _createContainer(); addTearDown(container.dispose); @@ -1040,20 +1026,15 @@ void main() { }); for (final (name, reason, expected) in [ - ( - 'a missing origin or landing', - lineupLinkEndMissingMessage, - "origin or landing spot isn't on this page in the cloud", - ), ( 'another page', lineupPageMismatchMessage, 'clashes with one on another page', ), ( - 'an origin or landing another lineup uses', - lineupEndInUseMessage, - 'still uses this origin or landing spot, so it was not deleted', + 'its old cloud format', + retiredLineupOpMessage, + 'saved by an older version of Icarus', ), ]) { testWidgets('a lineup refused for $name says why, not "another edit"', @@ -1101,7 +1082,7 @@ void main() { addTearDown(container.dispose); container .read(strategySaveStateProvider.notifier) - .setCloudSyncError(lineupLinkEndMissingMessage); + .setCloudSyncError(lineupPageMismatchMessage); await tester.pumpWidget( UncontrolledProviderScope( @@ -1116,7 +1097,7 @@ void main() { await tester.tap(_syncButton('attention')); await tester.pumpAndSettle(); - expect(find.textContaining('on this page in the cloud'), findsOneWidget); + expect(find.textContaining('another page'), findsOneWidget); expect( find.textContaining('applies to all 2 changes that need attention'), findsOneWidget, @@ -1132,7 +1113,7 @@ void main() { ); addTearDown(container.dispose); container.read(strategySaveStateProvider.notifier).setCloudSyncError( - '$lineupLinkEndMissingMessage. $otherWorkNeedsAttentionNote', + '$lineupPageMismatchMessage. $otherWorkNeedsAttentionNote', ); await tester.pumpWidget( @@ -1148,7 +1129,7 @@ void main() { await tester.tap(_syncButton('attention')); await tester.pumpAndSettle(); - expect(find.textContaining('on this page in the cloud'), findsOneWidget); + expect(find.textContaining('another page'), findsOneWidget); expect( find.textContaining('Other changes here were not saved either'), findsOneWidget, @@ -1201,14 +1182,14 @@ void main() { const ConflictResolution( type: ConflictResolutionType.rebase, opId: 'refused-link', - message: lineupLinkEndMissingMessage, + message: lineupPageMismatchMessage, ), ); await tester.pump(); await tester.pump(const Duration(milliseconds: 300)); expect( - toast(friendlyCloudSyncError(lineupLinkEndMissingMessage)), + toast(friendlyCloudSyncError(lineupPageMismatchMessage)), findsOneWidget, ); expect( diff --git a/tool/convex_client_gauntlet/runtime/lib/workload.dart b/tool/convex_client_gauntlet/runtime/lib/workload.dart index bd78ca06..37130caa 100644 --- a/tool/convex_client_gauntlet/runtime/lib/workload.dart +++ b/tool/convex_client_gauntlet/runtime/lib/workload.dart @@ -4,7 +4,7 @@ import 'package:crypto/crypto.dart'; const operationsPerSeed = 1000; const operationBatchSize = 50; -const cloudProtocolVersion = 4; +const cloudProtocolVersion = 5; const payloadVersion = 1; const baseFixturePath = 'test/fixtures/strategy_integrity/base-test-v43.ica'; const baseFixtureSha256 = @@ -47,9 +47,9 @@ List> buildOperationTrace(int seed) { _addRevisionCycle( add: add, entity: _ContentEntity.lineup, - // Lineup rows are keyed `:`. + // A lineup row is keyed by the lineup's id. publicId: - 'lineupOrigin:${seedPrefix(seed)}lineup-${cycle.toString().padLeft(3, '0')}', + '${seedPrefix(seed)}lineup-${cycle.toString().padLeft(3, '0')}', pagePublicId: initialPageId(seed), payloadBuilder: (variant) => _lineupPayload(seed, cycle, variant), initialSortIndex: 1000 + cycle, @@ -262,21 +262,42 @@ Map _utilityPayload(int seed, int cycle, int variant) { Map _lineupPayload(int seed, int cycle, int variant) { final id = '${seedPrefix(seed)}lineup-${cycle.toString().padLeft(3, '0')}'; - // One lineup origin row; each revision cycle moves its agent. + // One lineup with its origin and landing; each revision cycle moves its + // agent. return { - 'kind': 'lineupOrigin', + 'kind': 'lineup', 'payloadVersion': payloadVersion, 'data': { 'id': id, - 'agent': { - 'id': '$id-agent', - 'isDeleted': false, - 'position': {'dx': 10 + cycle, 'dy': 20 + variant}, - 'type': 'sova', - 'isAlly': true, - 'state': 'none', - 'kind': 'plain', - 'lineUpID': id, + 'name': 'Lineup $cycle', + 'youtubeLink': '', + 'notes': '', + 'images': [], + 'origin': { + 'id': '$id-origin', + 'agent': { + 'id': '$id-agent', + 'isDeleted': false, + 'position': {'dx': 10 + cycle, 'dy': 20 + variant}, + 'type': 'sova', + 'isAlly': true, + 'state': 'none', + 'kind': 'plain', + 'lineUpID': '$id-origin', + }, + }, + 'landing': { + 'id': '$id-landing', + 'ability': { + 'id': '$id-ability', + 'isDeleted': false, + 'position': {'dx': 40 + cycle, 'dy': 50}, + 'data': {'type': 'sova', 'index': 1}, + 'isAlly': true, + 'rotation': 0, + 'length': 0, + 'lineUpID': '$id-landing', + }, }, }, }; From 3ae816e48ead201d5476460b3f832cb329f26e9d Mon Sep 17 00:00:00 2001 From: Dara Adedeji Date: Thu, 1 Oct 2026 23:25:39 -0400 Subject: [PATCH 03/14] Version shared lineup spots, refuse edits to deleted rows, gate old writes Copies of a shared spot can drift apart (a move that reached only some of its lineups), and the row revision is no measure of which copy is newest. Each end of a lineup row now carries its spot's version, an integer from 1; clients draw the copy with the highest version, then the greatest lineup id. Duplicate copies versions as they are, so a copy draws every spot where the original does. A patch or reorder of a deleted element or lineup was acked onto the tombstone and the edit vanished on the next load. It is now rejected with the new reason "deleted"; an add expecting the tombstone's revision brings the row back. A write that changes nothing on a live row stays a noop whatever revision it expects, so two clients healing the same row never conflict. Clients on protocol 4 still send checkLineupLinkEnds/checkLineupEndDeletes and graph rows. The args are accepted and ignored, and the old kinds pass argument validation to be refused per op in the handler, so such a client gets CLIENT_UPGRADE_REQUIRED rather than a validation error. Storage takes only lineups. The agent summary is refreshed only after a batch that touched a page, an agent element or a lineup. Co-Authored-By: Claude Opus 5.5 (1M context) --- convex/function_spec.json | 25472 +++++++++++++++++++++++---- convex/lib/opTypes.ts | 8 +- convex/lib/payloadValidators.ts | 25 + convex/lib/strategyAgentSummary.ts | 8 +- convex/lineups.test.ts | 342 +- convex/ops.ts | 68 +- convex/strategyDuplicate.test.ts | 147 +- convex/syncBoundaries.test.ts | 291 +- convex/testContent.helpers.ts | 12 +- 9 files changed, 23369 insertions(+), 3004 deletions(-) diff --git a/convex/function_spec.json b/convex/function_spec.json index 6b04ef03..10bf49b0 100644 --- a/convex/function_spec.json +++ b/convex/function_spec.json @@ -48413,6 +48413,18 @@ }, "optional": true }, + "checkLineupEndDeletes": { + "fieldType": { + "type": "boolean" + }, + "optional": true + }, + "checkLineupLinkEnds": { + "fieldType": { + "type": "boolean" + }, + "optional": true + }, "checkTrashedPageDeletes": { "fieldType": { "type": "boolean" @@ -88144,126 +88156,284 @@ }, "payload": { "fieldType": { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" - }, - "type": "record", - "values": { + "type": "union", + "value": [ + { + "type": "object", + "value": { + "data": { "fieldType": { - "type": "union", - "value": [ - { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -88289,16 +88459,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -88313,16 +88534,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -88330,61 +88602,67 @@ } } ] - } + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { "type": "union", "value": [ { @@ -88401,46 +88679,142 @@ } ] }, - "optional": false - } + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -88466,16 +88840,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -88490,32 +88915,15720 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + }, + "optional": false + }, + "kind": { + "fieldType": { + "type": "literal", + "value": "lineup" + }, + "optional": false + }, + "payloadVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "data": { + "fieldType": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + }, + "optional": false + }, + "kind": { + "fieldType": { + "type": "literal", + "value": "lineupOrigin" + }, + "optional": false + }, + "payloadVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "data": { + "fieldType": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + }, + "optional": false + }, + "kind": { + "fieldType": { + "type": "literal", + "value": "lineupLanding" + }, + "optional": false + }, + "payloadVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "data": { + "fieldType": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + }, + "optional": false + }, + "kind": { + "fieldType": { + "type": "literal", + "value": "lineupLink" + }, + "optional": false + }, + "payloadVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + } + ] + }, + "optional": false + }, + "sortIndex": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "type": { + "fieldType": { + "type": "literal", + "value": "lineup.add" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "expectedLineupRevision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "lineupPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "opId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "pagePublicId": { + "fieldType": { + "type": "string" + }, + "optional": true + }, + "payload": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "object", + "value": { + "data": { + "fieldType": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", "values": { "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + }, + "optional": false + }, + "kind": { + "fieldType": { + "type": "literal", + "value": "lineup" + }, + "optional": false + }, + "payloadVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "data": { + "fieldType": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -88578,73 +104691,480 @@ } ] }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -88670,16 +105190,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -88694,16 +105265,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -88711,37 +105333,94 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { @@ -88757,15 +105436,10 @@ "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -88781,144 +105455,149 @@ "type": "string" } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } - } - ] + ] + } }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { + { + "keys": { "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { + "keys": { "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false } - ] - } + } + ] }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] + "optional": false + } } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { @@ -88934,15 +105613,10 @@ "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -88958,79 +105632,129 @@ "type": "string" } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } - } - ] + ] + } }, - "optional": false - } - } - ] - }, - "optional": false - } + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -89078,16 +105802,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -89102,134 +105877,83 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { + "keys": { "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -89255,16 +105979,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -89279,16 +106054,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -89296,15 +106122,118 @@ } } ] - } + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, { - "keys": { - "type": "string" + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -89371,166 +106300,18 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { "type": "union", "value": [ { @@ -89546,15 +106327,10 @@ "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -89570,69 +106346,74 @@ "type": "string" } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { @@ -89648,15 +106429,10 @@ "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -89672,20 +106448,173 @@ "type": "string" } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } - } - ] + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, { - "keys": { - "type": "string" + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -89752,79 +106681,282 @@ } } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } - } - ] + ] + } }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -89894,16 +107026,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -89918,16 +107101,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -89969,16 +107203,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -89993,16 +107278,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -90071,102 +107407,6 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { "type": "union", "value": [ { @@ -90174,117 +107414,60 @@ }, { "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -90299,16 +107482,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -90350,16 +107584,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -90374,16 +107659,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -90396,202 +107732,97 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false } - ] - }, - "optional": false - } + } + ] + }, + "optional": false } - ] - } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + }, + "optional": false + }, + "kind": { + "fieldType": { + "type": "literal", + "value": "lineupOrigin" + }, + "optional": false + }, + "payloadVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "data": { + "fieldType": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, { - "keys": { - "type": "string" + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -91111,9 +108342,519 @@ "keys": { "type": "string" }, - "type": "record", - "values": { - "fieldType": { + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -91180,20 +108921,122 @@ } } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -91362,147 +109205,207 @@ } } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } - } - ] - }, - "optional": false + ] + }, + "optional": false + } } - } - ] - }, - "optional": false + ] + }, + "optional": false + } } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "lineup" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false - } - } - }, - "optional": false - }, - "sortIndex": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "type": { - "fieldType": { - "type": "literal", - "value": "lineup.add" - }, - "optional": false - } - } - }, - { - "type": "object", - "value": { - "expectedLineupRevision": { - "fieldType": { - "type": "number" - }, - "optional": false - }, - "lineupPublicId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "opId": { - "fieldType": { - "type": "string" - }, - "optional": false - }, - "pagePublicId": { - "fieldType": { - "type": "string" - }, - "optional": true - }, - "payload": { - "fieldType": { - "type": "object", - "value": { - "data": { - "fieldType": { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + ] + } }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -91594,16 +109497,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -91618,16 +109572,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -91669,16 +109674,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -91693,16 +109749,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -91771,16 +109878,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -91795,16 +109953,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -91846,16 +110055,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -91870,16 +110130,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -91975,16 +110286,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -91999,16 +110361,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -92050,16 +110463,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -92074,16 +110538,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -92152,16 +110667,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -92176,16 +110742,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -92227,16 +110844,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -92251,16 +110919,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -92283,236 +111002,131 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + "optional": false + } + } + ] + }, + "optional": false + } + }, + "optional": false + }, + "kind": { + "fieldType": { + "type": "literal", + "value": "lineupLanding" + }, + "optional": false + }, + "payloadVersion": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "data": { + "fieldType": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" }, { - "type": "array", - "value": { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - } + "type": "boolean" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -92681,86 +111295,40 @@ } } ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + } }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { @@ -92776,44 +111344,192 @@ "type": "string" } ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -92880,20 +111596,326 @@ } } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } - } - ] + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, { - "keys": { - "type": "string" + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -93028,15 +112050,168 @@ "type": "string" } ] - } - }, - { - "keys": { - "type": "string" + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -93052,106 +112227,54 @@ "type": "string" } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } - } - ] - }, - "optional": false + ] + }, + "optional": false + } } - } - ] - }, - "optional": false + ] + }, + "optional": false + } } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + ] + } }, { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -93199,16 +112322,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -93223,16 +112397,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -93274,16 +112499,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -93298,16 +112574,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -93320,112 +112647,113 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" + }, + { + "type": "boolean" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "number" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -93492,115 +112820,18 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { "type": "union", "value": [ { @@ -93617,41 +112848,92 @@ } ] }, - "optional": false - } - } - ] + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { @@ -93667,15 +112949,10 @@ "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -93691,93 +112968,47 @@ "type": "string" } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } - } - ] + ] + } }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { + { + "keys": { "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { "type": "union", "value": [ { @@ -93794,19 +113025,116 @@ } ] }, - "optional": false - } + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -93873,96 +113201,95 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } ] + } + }, + { + "keys": { + "type": "string" }, - { - "type": "array", - "value": { + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { @@ -93988,16 +113315,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] } @@ -94012,16 +113390,67 @@ "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, { - "type": "number" + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } }, { - "type": "string" + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } ] }, @@ -94029,192 +113458,91 @@ } } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } + "optional": false } - ] - } + } + ] }, - { - "keys": { - "type": "string" + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "optional": false - } + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -94281,115 +113609,18 @@ } } ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { "type": "union", "value": [ { @@ -94406,41 +113637,92 @@ } ] }, - "optional": false - } - } - ] + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { "type": "union", "value": [ { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] + "type": "null" }, { - "type": "array", - "value": { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { "type": "union", "value": [ { @@ -94456,15 +113738,10 @@ "type": "string" } ] - } - }, - { - "keys": { - "type": "string" }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -94480,93 +113757,47 @@ "type": "string" } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } } - } - ] + ] + } }, - "optional": false - } - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { - "type": "union", - "value": [ - { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { + { + "keys": { "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { + }, + "type": "record", + "values": { + "fieldType": { "type": "union", "value": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - { - "type": "array", - "value": { - "type": "union", - "value": [ - { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - { - "keys": { - "type": "string" - }, - "type": "record", - "values": { - "fieldType": { "type": "union", "value": [ { @@ -94583,19 +113814,116 @@ } ] }, - "optional": false - } - } - ] + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" }, { - "keys": { - "type": "string" + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] }, - "type": "record", - "values": { - "fieldType": { + { + "type": "array", + "value": { "type": "union", "value": [ { @@ -94662,51 +113990,305 @@ } } ] + } + }, + { + "keys": { + "type": "string" }, - "optional": false + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } } - } - ] + ] + } }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] - }, - "optional": false - } - } - ] + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + } + ] + }, + "optional": false + } + }, + "optional": false + }, + "kind": { + "fieldType": { + "type": "literal", + "value": "lineupLink" + }, + "optional": false + }, + "payloadVersion": { + "fieldType": { + "type": "number" }, "optional": false } - }, - "optional": false - }, - "kind": { - "fieldType": { - "type": "literal", - "value": "lineup" - }, - "optional": false - }, - "payloadVersion": { - "fieldType": { - "type": "number" - }, - "optional": false + } } - } + ] }, "optional": true }, @@ -117963,6 +137545,10 @@ "type": "literal", "value": "already_exists" }, + { + "type": "literal", + "value": "deleted" + }, { "type": "literal", "value": "element_strategy_mismatch" diff --git a/convex/lib/opTypes.ts b/convex/lib/opTypes.ts index 6f1b7c76..53e1edc5 100644 --- a/convex/lib/opTypes.ts +++ b/convex/lib/opTypes.ts @@ -1,6 +1,7 @@ import { v, type Infer } from "convex/values"; import { elementPayloadValidator, + lineupOpPayloadValidator, lineupPayloadValidator, mapThemePaletteValidator, pagePayloadValidator, @@ -97,7 +98,7 @@ const lineupAddOpValidator = v.object({ type: v.literal("lineup.add"), lineupPublicId: v.string(), pagePublicId: v.string(), - payload: lineupPayloadValidator, + payload: lineupOpPayloadValidator, sortIndex: v.number(), expectedLineupRevision: v.optional(v.number()), }); @@ -107,7 +108,7 @@ const lineupPatchOpValidator = v.object({ type: v.literal("lineup.patch"), lineupPublicId: v.string(), pagePublicId: v.optional(v.string()), - payload: v.optional(lineupPayloadValidator), + payload: v.optional(lineupOpPayloadValidator), sortIndex: v.optional(v.number()), expectedLineupRevision: v.number(), }); @@ -150,6 +151,9 @@ export type StrategyOp = Infer; export const opRejectionReasonValidator = v.union( v.literal("already_exists"), + // A patch or reorder of a row a teammate deleted: only an add with the + // tombstone's revision brings it back. + v.literal("deleted"), v.literal("element_strategy_mismatch"), v.literal("lineup_strategy_mismatch"), v.literal("missing_expected_revision"), diff --git a/convex/lib/payloadValidators.ts b/convex/lib/payloadValidators.ts index ed260155..92400a0c 100644 --- a/convex/lib/payloadValidators.ts +++ b/convex/lib/payloadValidators.ts @@ -125,3 +125,28 @@ export const lineupPayloadValidator = v.object({ payloadVersion: v.number(), data: cloudJsonObjectValidator, }); + +// What a lineup op may carry as an argument: a lineup, or a row of the graph +// clients on protocol 4 and earlier wrote. Convex checks arguments before +// the handler runs, so refusing the old kinds here would answer an old +// client with a validation error instead of CLIENT_UPGRADE_REQUIRED from the +// protocol gate. The handler refuses them (see assertLineupPayload in +// ops.ts) and storage takes only lineups. +export const lineupOpPayloadValidator = v.union( + lineupPayloadValidator, + v.object({ + kind: v.literal("lineupOrigin"), + payloadVersion: v.number(), + data: cloudJsonObjectValidator, + }), + v.object({ + kind: v.literal("lineupLanding"), + payloadVersion: v.number(), + data: cloudJsonObjectValidator, + }), + v.object({ + kind: v.literal("lineupLink"), + payloadVersion: v.number(), + data: cloudJsonObjectValidator, + }), +); diff --git a/convex/lib/strategyAgentSummary.ts b/convex/lib/strategyAgentSummary.ts index 79d4f73a..d0242a1e 100644 --- a/convex/lib/strategyAgentSummary.ts +++ b/convex/lib/strategyAgentSummary.ts @@ -23,10 +23,10 @@ export async function refreshStrategyAgentSummary( ctx: MutationCtx, strategyId: Id<"strategies">, ): Promise { - // Only agent elements and live lineups carry an agent; reading just those - // keeps this cheap however large the strategy's other content is (it runs - // after every batch of content ops). A lineup row holds its images' ids, - // never their bytes. + // Only agent elements and live lineups carry an agent, so only they are + // read. That still reads every live lineup row, image ids and all (never + // image bytes), so callers refresh only when a change may have touched an + // agent: applyBatch skips batches without page, agent or lineup changes. const agents = await ctx.db .query("elements") .withIndex("by_strategyId_and_elementType", (q) => diff --git a/convex/lineups.test.ts b/convex/lineups.test.ts index 54ca72e2..dc4f6464 100644 --- a/convex/lineups.test.ts +++ b/convex/lineups.test.ts @@ -9,7 +9,11 @@ import type { DataModel } from "./_generated/dataModel"; import { markAssetReferencesReady } from "./lib/assetReferences"; import { CURRENT_CLOUD_PROTOCOL_VERSION } from "./lib/cloudProtocol"; import schema from "./schema"; -import { lineupPayload, type TestLineup } from "./testContent.helpers"; +import { + insertLineup, + lineupPayload, + type TestLineup, +} from "./testContent.helpers"; import { modules } from "./test.setup"; const ensureCurrentUser = makeFunctionReference<"mutation">( @@ -572,28 +576,219 @@ describe("a lineup row must be drawable on its own", () => { expect(row.payload).toEqual(fanIn[0]!.payload); }); - test("a row of an old graph kind is refused by the contract", async () => { + test("a row of an old graph kind is refused by the handler, the legacy group by the contract", async () => { + const { owner } = await createHarness(); + // Protocol 4's graph rows get past argument validation so an old client + // reaches the protocol gate; on protocol 5 the handler refuses each op. + const graphKinds = ["lineupOrigin", "lineupLanding", "lineupLink"]; + const results = await apply( + owner, + "old-shapes", + graphKinds.map((kind) => ({ + opId: `add-${kind}`, + type: "lineup.add", + lineupPublicId: `${kind}:k`, + pagePublicId, + payload: { + kind, + payloadVersion: 1, + data: { id: "k", originId: "o", landingId: "l", images: [] }, + }, + sortIndex: 0, + })), + ); + expect(results).toHaveLength(graphKinds.length); + for (const [index, kind] of graphKinds.entries()) { + expect(results[index]).toMatchObject({ + opId: `add-${kind}`, + status: "failed", + code: "INVALID_LINEUP_PAYLOAD_KIND", + }); + } + // The group of protocol 3 and earlier is still refused whole. + await expect( + apply(owner, "old-client", [ + { + opId: "add-lineupGroup", + type: "lineup.add", + lineupPublicId: "group", + pagePublicId, + payload: { + kind: "lineupGroup", + payloadVersion: 1, + data: { id: "group", items: [{ id: "item" }] }, + }, + sortIndex: 0, + }, + ]), + ).rejects.toThrow(/Validator error|ArgumentValidationError/); + expect(await pageLineups(owner)).toEqual([]); + }); + + test("an end without a whole-number spot version of at least 1 is refused", async () => { const { owner } = await createHarness(); - for (const kind of ["lineupLink", "lineupOrigin", "lineupGroup"]) { - await expect( - apply(owner, "old-client", [ + const valid = lineupPayload("k"); + // Each case puts [version] on one end (undefined drops the field). + const cases: Array<["origin" | "landing", unknown]> = []; + for (const end of ["origin", "landing"] as const) { + for (const version of [undefined, 0, -1, 1.5, "2", null]) { + cases.push([end, version]); + } + } + const ops = cases.map(([end, version], index) => { + const id = `bad-version-${index}`; + const payload = lineupPayload(id); + const withVersion: Record = { ...payload.data[end] }; + if (version === undefined) { + delete withVersion.version; + } else { + withVersion.version = version; + } + return { + ...addOp(lineup(id), index), + payload: { ...payload, data: { ...payload.data, [end]: withVersion } }, + }; + }); + const results = await apply(owner, "owner-client", [ + ...ops, + addOp({ key: "k", payload: valid }, cases.length), + ]); + expect(results).toHaveLength(cases.length + 1); + for (const result of results.slice(0, cases.length)) { + expect(result).toMatchObject({ + status: "failed", + code: "INVALID_LINEUP_PAYLOAD_DATA", + message: "Lineup has no versioned origin or landing", + }); + } + expect(results[cases.length]).toMatchObject({ status: "applied" }); + expect(liveKeys(await pageLineups(owner))).toEqual(["k"]); + + // A patch that drops a version is refused too, leaving the row as it was. + const { version: _version, ...unversioned } = valid.data.landing; + const patched = await apply(owner, "owner-client", [ + { + ...patchOp("drop-version", { key: "k", payload: valid }, 1), + payload: { ...valid, data: { ...valid.data, landing: unversioned } }, + }, + ]); + expect(patched[0]).toMatchObject({ + status: "failed", + code: "INVALID_LINEUP_PAYLOAD_DATA", + }); + const row = (await pageLineups(owner))[0]!; + expect(row).toMatchObject({ revision: 1 }); + expect(row.payload).toEqual(valid); + }); + + test("each end's spot version is stored and read back as written", async () => { + const { owner } = await createHarness(); + const row = lineup("versioned", { originVersion: 4, landingVersion: 9 }); + await apply(owner, "owner-client", [addOp(row, 0)]); + const stored = (await pageLineups(owner))[0]!; + expect(stored.payload.data).toMatchObject({ + origin: { version: 4 }, + landing: { version: 9 }, + }); + }); +}); + +describe("clients on protocol 4", () => { + test("a protocol 4 batch with its lineup options and a link row is told to upgrade", async () => { + const { owner } = await createHarness(); + const error = await owner + .mutation(applyBatch, { + strategyPublicId, + clientId: "protocol-4-client", + clientProtocolVersion: 4, + checkLineupLinkEnds: true, + checkLineupEndDeletes: true, + ops: [ { - opId: `add-${kind}`, + opId: "add-link", type: "lineup.add", - lineupPublicId: `${kind}:k`, + lineupPublicId: "lineupLink:k", pagePublicId, payload: { - kind, + kind: "lineupLink", payloadVersion: 1, data: { id: "k", originId: "o", landingId: "l", images: [] }, }, sortIndex: 0, }, - ]), - ).rejects.toThrow(/Validator error|ArgumentValidationError/); - } + ], + }) + .then( + () => null, + (caught: unknown) => caught as { data?: unknown; message?: string }, + ); + expect(error).not.toBeNull(); + // The protocol gate answered, not argument validation. + expect(error?.message ?? "").not.toMatch( + /Validator error|ArgumentValidationError/, + ); + expect(typeof error?.data).toBe("string"); + expect(JSON.parse(error?.data as string)).toMatchObject({ + code: "CLIENT_UPGRADE_REQUIRED", + }); expect(await pageLineups(owner)).toEqual([]); }); + + test("a protocol 5 batch with an origin row fails that op and applies the rest", async () => { + const { owner } = await createHarness(); + const response = (await owner.mutation(applyBatch, { + strategyPublicId, + clientId: "mixed-client", + clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, + // Still accepted (and ignored) on protocol 5. + checkLineupLinkEnds: true, + checkLineupEndDeletes: true, + ops: [ + { + opId: "add-origin", + type: "lineup.add", + lineupPublicId: "lineupOrigin:o", + pagePublicId, + payload: { + kind: "lineupOrigin", + payloadVersion: 1, + data: { id: "o", agent: { type: "sova" } }, + }, + sortIndex: 0, + }, + { + opId: "add-text", + type: "element.add", + elementPublicId: "note", + pagePublicId, + payload: { + kind: "text", + payloadVersion: 1, + data: { id: "note", elementType: "text", text: "Default" }, + }, + sortIndex: 0, + }, + ], + })) as { results: Result[] }; + expect(response.results).toMatchObject([ + { + opId: "add-origin", + status: "failed", + code: "INVALID_LINEUP_PAYLOAD_KIND", + }, + { opId: "add-text", status: "applied", appliedRevision: 1 }, + ]); + const snapshot = (await owner.query(getPageSnapshot, { + clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, + strategyPublicId, + pagePublicId, + })) as { + elements: Array<{ publicId: string }>; + lineups: LineupRow[]; + }; + expect(snapshot.elements.map((row) => row.publicId)).toEqual(["note"]); + expect(snapshot.lineups).toEqual([]); + }); }); describe("lineup row keys belong to their strategy and page", () => { @@ -806,4 +1001,129 @@ describe("agent summary", () => { ]); expect(await agentSummary(t)).toEqual(["viper"]); }); + + test("only a batch that may change the agents refreshes the summary", async () => { + const { t, owner } = await createHarness(); + // A lineup written straight to the table leaves the stored summary + // stale, so a refresh shows as the summary catching up with it. + await seedLineupRow(t, "seeded-viper", "viper"); + expect(await agentSummary(t)).toEqual([]); + + // Drawings, utilities and text never carry an agent: no refresh. + const plain = await apply(owner, "owner-client", [ + elementAdd("drawing-1", "drawing", 0), + elementAdd("utility-1", "utility", 1), + elementAdd("text-1", "text", 2), + { + opId: "patch-text-1", + type: "element.patch", + elementPublicId: "text-1", + pagePublicId, + payload: elementPayload("text-1", "text", { text: "Edited" }), + expectedElementRevision: 1, + }, + { + opId: "delete-drawing-1", + type: "element.delete", + elementPublicId: "drawing-1", + pagePublicId, + expectedElementRevision: 1, + }, + ]); + expect(plain.map((result) => result.status)).toEqual([ + "applied", + "applied", + "applied", + "applied", + "applied", + ]); + expect(await agentSummary(t)).toEqual([]); + + // Placing an agent does. + await apply(owner, "owner-client", [elementAdd("jett-1", "agent", 3)]); + expect(await agentSummary(t)).toEqual(["jett", "viper"]); + + // Deleting an agent does too, though the op carries no payload. + await seedLineupRow(t, "seeded-astra", "astra"); + expect(await agentSummary(t)).toEqual(["jett", "viper"]); + await apply(owner, "owner-client", [ + { + opId: "delete-jett-1", + type: "element.delete", + elementPublicId: "jett-1", + pagePublicId, + expectedElementRevision: 1, + }, + ]); + expect(await agentSummary(t)).toEqual(["astra", "viper"]); + + // And so does any lineup change. + await seedLineupRow(t, "seeded-kayo", "kayo"); + expect(await agentSummary(t)).toEqual(["astra", "viper"]); + await apply(owner, "owner-client", [ + addOp(lineup("placed-sova", { agentType: "sova" }), 9), + ]); + expect(await agentSummary(t)).toEqual(["astra", "kayo", "sova", "viper"]); + }); }); + +function elementPayload( + id: string, + kind: "agent" | "drawing" | "text" | "utility", + extra: Record = {}, +) { + return { + kind, + payloadVersion: 1, + data: { + id, + elementType: kind, + ...(kind === "agent" ? { type: "jett" } : {}), + ...extra, + }, + }; +} + +function elementAdd( + id: string, + kind: "agent" | "drawing" | "text" | "utility", + sortIndex: number, +) { + return { + opId: `add-${id}`, + type: "element.add", + elementPublicId: id, + pagePublicId, + payload: elementPayload(id, kind), + sortIndex, + }; +} + +/// Writes a live lineup straight to the table, bypassing applyBatch and so +/// the agent summary refresh. +async function seedLineupRow(t: RootHarness, id: string, agentType: string) { + await t.run(async (ctx) => { + const strategy = await ctx.db + .query("strategies") + .withIndex("by_publicId", (q) => q.eq("publicId", strategyPublicId)) + .unique(); + const page = await ctx.db + .query("pages") + .withIndex("by_strategyId", (q) => q.eq("strategyId", strategy!._id)) + .first(); + const now = Date.now(); + await insertLineup(ctx, { + publicId: id, + strategyId: strategy!._id, + pageId: page!._id, + payloadKind: "lineup", + payloadVersion: 1, + payload: lineupPayload(id, { agentType }), + sortIndex: 0, + revision: 1, + deleted: false, + createdAt: now, + updatedAt: now, + }); + }); +} diff --git a/convex/ops.ts b/convex/ops.ts index 33152592..e358f084 100644 --- a/convex/ops.ts +++ b/convex/ops.ts @@ -341,7 +341,9 @@ function invalidLineupData(message: string) { /// Checks one lineup row before it is stored. A row is one whole lineup, /// keyed by its id, holding its origin and landing: every row can be drawn -/// on its own, whatever other rows exist. +/// on its own, whatever other rows exist. Each end carries its spot's +/// version, which decides the copy drawn when lineups sharing the spot +/// disagree (see lineUpGraphFromCloudRows on the client). function assertLineupPayload( payload: unknown, lineupPublicId: string, @@ -349,8 +351,9 @@ function assertLineupPayload( if (!isRecord(payload)) { throw errorWithCode("MISSING_LINEUP_PAYLOAD", "Missing lineup payload"); } - // Argument validation already refuses any other kind; this check keeps - // the rule where the row is written. + // Argument validation lets the graph rows of protocol 4 through, so an old + // client reaches the protocol gate (see lineupOpPayloadValidator); none is + // ever stored. if (payload.kind !== "lineup") { throw errorWithCode( "INVALID_LINEUP_PAYLOAD_KIND", @@ -379,19 +382,36 @@ function assertLineupPayload( // The row must carry both ends hydration draws; a row without them would // load as nothing and read as a deletion nobody made. if (!hasEnd(data.origin, "agent") || !hasEnd(data.landing, "ability")) { - throw invalidLineupData("Lineup has no origin or landing"); + throw invalidLineupData("Lineup has no versioned origin or landing"); } return payload as LineupPayload; } -/// Whether [end] is an origin or landing: an id and the marker it places -/// under [markerKey]. +/// Whether [end] is an origin or landing: an id, the marker it places under +/// [markerKey], and its spot's version (an integer from 1). function hasEnd(end: unknown, markerKey: "agent" | "ability"): boolean { return ( isRecord(end) && typeof end.id === "string" && end.id.length > 0 && - isRecord(end[markerKey]) + isRecord(end[markerKey]) && + typeof end.version === "number" && + Number.isInteger(end.version) && + end.version >= 1 + ); +} + +/// A patch or reorder of a row a teammate deleted. Changing the tombstone +/// would ack an edit nobody sees, so it is refused; an add expecting the +/// tombstone's revision brings the row back instead ("Keep mine"). +function refuseChangeToDeleted( + row: Doc<"elements"> | Doc<"lineups">, +): OperationResult | null { + if (!row.deleted) return null; + return rejected( + "deleted", + { revision: row.revision, payload: row.payload }, + row.pageId, ); } @@ -624,6 +644,7 @@ function isRejectionReason( ): reason is Extract["reason"] { return ( reason === "already_exists" || + reason === "deleted" || reason === "element_strategy_mismatch" || reason === "lineup_strategy_mismatch" || reason === "missing_expected_revision" || @@ -1182,6 +1203,8 @@ async function applyElementOp( return rejected("not_found"); } await assertContentPageLive(ctx, existing.pageId); + const deleted = refuseChangeToDeleted(existing); + if (deleted !== null) return deleted; const patch: Record = {}; let eventPageId = existing.pageId; if (op.kind === "patch") { @@ -1355,6 +1378,8 @@ async function applyLineupOp( return rejected("not_found"); } await assertContentPageLive(ctx, existing.pageId); + const deleted = refuseChangeToDeleted(existing); + if (deleted !== null) return deleted; const patch: Record = {}; let eventPageId = existing.pageId; if (op.kind === "patch") { @@ -1421,6 +1446,10 @@ async function applyLineupOp( return { status: "ack", appliedRevision: revision, eventPageId }; } +function isAgentRow(row: Doc<"elements"> | Doc<"lineups"> | null): boolean { + return row !== null && "elementType" in row && row.elementType === "agent"; +} + /// The element or lineup row an op targets in this strategy, if any. async function contentRowForOp( ctx: MutationCtx, @@ -1504,6 +1533,12 @@ export const applyBatch = mutation({ // once the page is back. Older clients get the no-op a deleted page's // purged content gave them (see refuseDeleteOffLivePage). checkTrashedPageDeletes: v.optional(v.boolean()), + // Sent by clients on protocol 4, which stored lineups as origin, + // landing and link rows. Ignored: accepting them lets such a client + // reach the protocol gate (CLIENT_UPGRADE_REQUIRED) instead of failing + // argument validation. + checkLineupLinkEnds: v.optional(v.boolean()), + checkLineupEndDeletes: v.optional(v.boolean()), // Set by clients that bind a batch to the account whose outbox holds // it: that account's identity subject. A batch can reach the server // under a different sign-in than the one that queued it (the transport @@ -1522,7 +1557,10 @@ export const applyBatch = mutation({ const { user } = await assertStrategyRole(ctx, strategy, "editor"); const results: PublicOperationResult[] = []; let acceptedStrategyBatchBaseRevision: number | undefined; - let contentChanged = false; + // Whether an accepted op may have changed which agents the strategy + // uses: a page added, moved to or out of the trash, an agent element, + // or a lineup. + let agentsMayHaveChanged = false; // Images deleted elements showed, whose upload placeholders may go once // the whole batch has applied and nothing shows them any more. const placeholderCandidates = new Set(); @@ -1616,8 +1654,16 @@ export const applyBatch = mutation({ args.checkTrashedPageDeletes === true, ); } - if (result.status === "ack" && op.entityType !== "strategy") { - contentChanged = true; + if ( + result.status === "ack" && + (op.entityType === "page" || + op.entityType === "lineup" || + (op.entityType === "element" && + (isAgentRow(rowBefore) || + (op.payload as { kind?: unknown } | undefined)?.kind === + "agent"))) + ) { + agentsMayHaveChanged = true; } if (result.status === "ack") { await reconcileExpectedAssets( @@ -1688,7 +1734,7 @@ export const applyBatch = mutation({ // Checked against the batch's final state, once. await removeUploadPlaceholders(ctx, strategy._id, placeholderCandidates); - if (contentChanged) { + if (agentsMayHaveChanged) { await refreshStrategyAgentSummary(ctx, strategy._id); } diff --git a/convex/strategyDuplicate.test.ts b/convex/strategyDuplicate.test.ts index 988db05a..fb597220 100644 --- a/convex/strategyDuplicate.test.ts +++ b/convex/strategyDuplicate.test.ts @@ -77,10 +77,15 @@ const seedLineupData = { name: "Shock dart", notes: "Two bounces", images: [{ id: "lineup-image" }], - origin: { id: "origin-1", agent: { type: "sova", lineUpID: "origin-1" } }, + origin: { + id: "origin-1", + agent: { type: "sova", lineUpID: "origin-1" }, + version: 1, + }, landing: { id: "item-1", ability: { type: "shock_dart", lineUpID: "item-1" }, + version: 1, }, }; @@ -233,6 +238,24 @@ function mockR2Deletes() { return fetchMock; } +/// The copy of a shared landing clients draw, as lineUpGraphFromCloudRows +/// picks it: the highest spot version, ties to the greatest lineup id. +function drawnLanding( + rows: Array<{ payload: { data: Record } }>, + landingId: string, +): Record { + const carriers = rows + .map((row) => row.payload.data) + .filter((data) => data.landing.id === landingId); + if (carriers.length === 0) throw new Error(`No lineup lands at ${landingId}`); + return carriers.reduce((best, data) => + data.landing.version > best.landing.version || + (data.landing.version === best.landing.version && data.id > best.id) + ? data + : best, + ).landing; +} + function deletedKeys(fetchMock: ReturnType): string[] { return fetchMock.mock.calls .map((call) => new URL(String(call[0])).pathname) @@ -338,10 +361,15 @@ describe("strategies:duplicate", () => { name: "Shock dart", notes: "Two bounces", images: [{ id: "lineup-image" }], - origin: { id: origin.id, agent: { type: "sova", lineUpID: origin.id } }, + origin: { + id: origin.id, + agent: { type: "sova", lineUpID: origin.id }, + version: 1, + }, landing: { id, ability: { type: "shock_dart", lineUpID: id }, + version: 1, }, }); @@ -564,6 +592,121 @@ describe("strategies:duplicate", () => { ); }); + test("a shared spot's copies keep their versions, so the copy draws the spot where the original did", async () => { + const { owner } = await createHarness(); + const versioned = "versions-source"; + const versionedPage = "versions-source-page"; + await owner.mutation(createStrategy, { + ...protocol, + publicId: versioned, + name: "Versions", + mapData: "split", + initialPagePublicId: versionedPage, + initialPageName: "Setup", + initialPageIsAttack: true, + }); + const atY = { dx: 300, dy: 300 }; + const atX = { dx: 100, dy: 100 }; + // Two lineups into one landing whose copies disagree on where it is. + // A holds the newer spot (version 5) on a row never edited since; + // B holds an older spot (version 3) on a row edited five times since, + // and has the greater lineup id. Drawing by row revision or by lineup + // id alone would pick X; by spot version the spot is at Y. + const lineupA = lineupPayload("lineup-a", { + originId: "origin-a", + landingId: "spot", + landingPosition: atY, + landingVersion: 5, + name: "A", + }); + const lineupB = (notes: string) => + lineupPayload("lineup-b", { + originId: "origin-b", + landingId: "spot", + landingPosition: atX, + landingVersion: 3, + name: "B", + notes, + }); + const ops: Array> = [ + ...[lineupA, lineupB("")].map((payload, index) => ({ + opId: `add-${payload.data.id}`, + type: "lineup.add", + lineupPublicId: payload.data.id, + pagePublicId: versionedPage, + payload, + sortIndex: index, + })), + ]; + for (let revision = 1; revision <= 5; revision++) { + ops.push({ + opId: `edit-b-${revision}`, + type: "lineup.patch", + lineupPublicId: "lineup-b", + pagePublicId: versionedPage, + payload: lineupB(`edit ${revision}`), + expectedLineupRevision: revision, + }); + } + const added = (await owner.mutation(applyBatch, { + ...protocol, + strategyPublicId: versioned, + clientId: "versions", + ops, + })) as { results: Array<{ status: string }> }; + expect(added.results.map((result) => result.status)).toEqual( + ops.map(() => "applied"), + ); + + type Row = { + publicId: string; + revision: number; + payload: { data: Record }; + }; + const lineupsOf = async (strategyPublicId: string) => + ( + (await owner.query(getFullSnapshot, { + ...protocol, + strategyPublicId, + })) as { lineups: Row[] } + ).lineups; + const original = await lineupsOf(versioned); + const byName = (rows: Row[], name: string) => + rows.find((row) => row.payload.data.name === name)!; + // The trap is set: B wins on revision and on id, A on version. + expect(byName(original, "A").revision).toBe(1); + expect(byName(original, "B").revision).toBe(6); + expect(drawnLanding(original, "spot").ability.position).toEqual(atY); + + await owner.mutation(duplicateStrategy, { + ...protocol, + sourceStrategyPublicId: versioned, + publicId: "versions-copy", + name: "Versions (Copy)", + }); + const copied = await lineupsOf("versions-copy"); + const copyA = byName(copied, "A").payload.data; + const copyB = byName(copied, "B").payload.data; + // One spot still, under a new id, each copy keeping its version and + // place. + expect(copyA.landing.id).toBe(copyB.landing.id); + expect(copyA.landing.id).not.toBe("spot"); + expect(copyA.landing).toMatchObject({ + version: 5, + ability: { position: atY }, + }); + expect(copyB.landing).toMatchObject({ + version: 3, + ability: { position: atX }, + }); + expect(copyA.origin.version).toBe(1); + expect(copyB.origin.version).toBe(1); + // So the copy draws the spot where the original did. + expect(drawnLanding(copied, copyA.landing.id).ability.position).toEqual( + atY, + ); + }); + test("an image whose tombstone is purged is reclaimed once nothing shows it", async () => { vi.useFakeTimers(); const fetchMock = mockR2Deletes(); diff --git a/convex/syncBoundaries.test.ts b/convex/syncBoundaries.test.ts index 0010f69c..4047c4a6 100644 --- a/convex/syncBoundaries.test.ts +++ b/convex/syncBoundaries.test.ts @@ -1189,9 +1189,19 @@ describe("record-scoped write contract", () => { expectedRevision: 2, }, ]); + // A patch never touches a tombstone, even one that would change nothing: + // only an add expecting the tombstone's revision brings the row back. expect(misclassifiedPatch.results).toMatchObject([ - { status: "noop", currentRevision: 2 }, - { status: "noop", currentRevision: 2 }, + { + status: "rejected", + reason: "deleted", + current: { type: "element", revision: 2 }, + }, + { + status: "rejected", + reason: "deleted", + current: { type: "lineup", revision: 2 }, + }, ]); const stillDeleted = (await owner.query(getPageSnapshot, { clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, @@ -1430,39 +1440,47 @@ describe("record-scoped write contract", () => { const { owner } = await createHarness(); await createBaseStrategy(owner); - // A protocol 4 graph row (an origin, landing or link) and the legacy - // group before it are both refused whole by argument validation. - for (const [entityPublicId, payload] of [ - [ - "lineupLink:k", - { + // A protocol 4 graph row (an origin, landing or link) passes argument + // validation, so an old client reaches the protocol gate, and the + // handler refuses it per op. + const graph = await applyOps(owner, "old-client", [ + { + opId: "add-lineupLink:k", + kind: "add", + entityType: "lineup", + entityPublicId: "lineupLink:k", + pagePublicId: pageA, + payload: { kind: "lineupLink", payloadVersion: 1, data: { id: "k", originId: "o", landingId: "l", images: [] }, }, - ], - [ - "legacy-group", + }, + ]); + expect(graph.results).toMatchObject([ + { + opId: "add-lineupLink:k", + status: "failed", + rawCode: "INVALID_LINEUP_PAYLOAD_KIND", + }, + ]); + // The legacy group before it is refused whole by argument validation. + await expect( + applyOps(owner, "old-client", [ { - kind: "lineupGroup", - payloadVersion: 1, - data: { id: "legacy-group", items: [{ id: "item" }] }, - }, - ], - ] as const) { - await expect( - applyOps(owner, "old-client", [ - { - opId: `add-${entityPublicId}`, - kind: "add", - entityType: "lineup", - entityPublicId, - pagePublicId: pageA, - payload, + opId: "add-legacy-group", + kind: "add", + entityType: "lineup", + entityPublicId: "legacy-group", + pagePublicId: pageA, + payload: { + kind: "lineupGroup", + payloadVersion: 1, + data: { id: "legacy-group", items: [{ id: "item" }] }, }, - ]), - ).rejects.toThrow(/Validator error|ArgumentValidationError/); - } + }, + ]), + ).rejects.toThrow(/Validator error|ArgumentValidationError/); const snapshot = (await owner.query(getPageSnapshot, { clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, strategyPublicId, @@ -1509,6 +1527,221 @@ describe("record-scoped write contract", () => { }); }); +/// A text element or a lineup named [label], to check the element and the +/// lineup write paths with the same steps. +function contentPayload(entity: "element" | "lineup", id: string, label: string) { + return entity === "element" + ? textPayload(label) + : lineupPayload(id, { name: label }); +} + +type ContentRow = { + publicId: string; + revision: number; + deleted: boolean; + sortIndex: number; + payload: unknown; +}; + +async function contentRow( + owner: Harness, + entity: "element" | "lineup", + id: string, +): Promise { + const snapshot = (await owner.query(getPageSnapshot, { + clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, + strategyPublicId, + pagePublicId: pageA, + })) as { elements: ContentRow[]; lineups: ContentRow[] }; + const rows = entity === "element" ? snapshot.elements : snapshot.lineups; + return rows.find((row) => row.publicId === id); +} + +describe("a row a teammate deleted", () => { + for (const entity of ["element", "lineup"] as const) { + test(`a patch or reorder of a deleted ${entity} is refused whatever it expects; an add expecting the tombstone restores it`, async () => { + const { owner } = await createHarness(); + await createBaseStrategy(owner); + const id = `deleted-${entity}`; + const base = contentPayload(entity, id, "base"); + const mine = contentPayload(entity, id, "mine"); + await applyOps(owner, "me", [ + { + opId: "add", + kind: "add", + entityType: entity, + entityPublicId: id, + pagePublicId: pageA, + payload: base, + }, + ]); + const deleted = await applyOps(owner, "teammate", [ + { + opId: "teammate-deletes", + kind: "delete", + entityType: entity, + entityPublicId: id, + expectedRevision: 1, + }, + ]); + expect(deleted.results).toMatchObject([ + { status: "applied", appliedRevision: 2 }, + ]); + const tombstone = await contentRow(owner, entity, id); + expect(tombstone).toMatchObject({ revision: 2, deleted: true }); + + const refused = { + status: "rejected", + reason: "deleted", + current: { type: entity, revision: 2, value: base }, + }; + // The edit made before the delete arrived. + const stale = await applyOps(owner, "me", [ + { + opId: "patch-expecting-1", + kind: "patch", + entityType: entity, + entityPublicId: id, + payload: mine, + expectedRevision: 1, + }, + ]); + expect(stale.results).toMatchObject([refused]); + // What an old "Keep mine" sent: the same edit against the tombstone. + const keepMine = await applyOps(owner, "me", [ + { + opId: "patch-expecting-2", + kind: "patch", + entityType: entity, + entityPublicId: id, + payload: mine, + expectedRevision: 2, + }, + { + opId: "identical-patch-expecting-2", + kind: "patch", + entityType: entity, + entityPublicId: id, + payload: base, + expectedRevision: 2, + }, + { + opId: "reorder-expecting-1", + kind: "reorder", + entityType: entity, + entityPublicId: id, + sortIndex: 5, + expectedRevision: 1, + }, + { + opId: "reorder-expecting-2", + kind: "reorder", + entityType: entity, + entityPublicId: id, + sortIndex: 5, + expectedRevision: 2, + }, + ]); + expect(keepMine.results).toMatchObject([ + refused, + refused, + refused, + refused, + ]); + // None of it touched the tombstone. + expect(await contentRow(owner, entity, id)).toEqual(tombstone); + + const restored = await applyOps(owner, "me", [ + { + opId: "restore", + kind: "add", + entityType: entity, + entityPublicId: id, + pagePublicId: pageA, + payload: mine, + expectedRevision: 2, + }, + ]); + expect(restored.results).toMatchObject([ + { status: "applied", appliedRevision: 3 }, + ]); + const row = await contentRow(owner, entity, id); + expect(row).toMatchObject({ revision: 3, deleted: false }); + expect(row!.payload).toEqual(mine); + }); + } +}); + +describe("a write that changes nothing", () => { + for (const entity of ["element", "lineup"] as const) { + test(`two clients sending the same ${entity} patch from one revision: one applies, the other is a noop`, async () => { + const { owner } = await createHarness(); + await createBaseStrategy(owner); + const id = `same-${entity}`; + const base = contentPayload(entity, id, "base"); + const edited = contentPayload(entity, id, "edited"); + await applyOps(owner, "setup", [ + { + opId: "add", + kind: "add", + entityType: entity, + entityPublicId: id, + pagePublicId: pageA, + payload: base, + }, + ]); + const patch = { + kind: "patch", + entityType: entity, + entityPublicId: id, + payload: edited, + expectedRevision: 1, + }; + const first = await applyOps(owner, "client-a", [ + { ...patch, opId: "a-edits" }, + ]); + const second = await applyOps(owner, "client-b", [ + { ...patch, opId: "b-edits" }, + ]); + expect(first.results).toMatchObject([ + { status: "applied", appliedRevision: 2 }, + ]); + expect(second.results).toMatchObject([ + { status: "noop", currentRevision: 2 }, + ]); + + // An add of what the live row already holds, on its page and at its + // place, is a noop too, whatever revision it expects. + const added = await applyOps(owner, "client-c", [ + { + opId: "c-adds-stale", + kind: "add", + entityType: entity, + entityPublicId: id, + pagePublicId: pageA, + payload: edited, + expectedRevision: 1, + }, + { + opId: "c-adds-unversioned", + kind: "add", + entityType: entity, + entityPublicId: id, + pagePublicId: pageA, + payload: edited, + }, + ]); + expect(added.results).toMatchObject([ + { status: "noop", currentRevision: 2 }, + { status: "noop", currentRevision: 2 }, + ]); + const row = await contentRow(owner, entity, id); + expect(row).toMatchObject({ revision: 2, deleted: false }); + expect(row!.payload).toEqual(edited); + }); + } +}); + describe("replay safety after operation event expiry", () => { test("identical add acknowledges and different add rejects", async () => { const { t, owner } = await createHarness(); diff --git a/convex/testContent.helpers.ts b/convex/testContent.helpers.ts index 18714806..8ea9d1cd 100644 --- a/convex/testContent.helpers.ts +++ b/convex/testContent.helpers.ts @@ -34,6 +34,11 @@ export type TestLineup = { agentType?: string; originPosition?: { dx: number; dy: number }; landingPosition?: { dx: number; dy: number }; + // Each end's spot version (default 1). Lineups sharing a spot draw it + // from the copy with the highest version, ties going to the greatest + // lineup id. + originVersion?: number; + landingVersion?: number; name?: string; youtubeLink?: string; notes?: string; @@ -42,8 +47,9 @@ export type TestLineup = { /// A lineup row's payload as a client writes it: the lineup's details and /// whole copies of its origin (the agent) and landing (the ability), each -/// marker's `lineUpID` naming its end. Lineups that share a spot pass the -/// same originId or landingId. The row's key is [id]. +/// marker's `lineUpID` naming its end, and each end its spot's version. +/// Lineups that share a spot pass the same originId or landingId. The row's +/// key is [id]. export function lineupPayload(id: string, lineup: TestLineup = {}) { const originId = lineup.originId ?? `${id}-origin`; const landingId = lineup.landingId ?? `${id}-landing`; @@ -64,6 +70,7 @@ export function lineupPayload(id: string, lineup: TestLineup = {}) { position: lineup.originPosition ?? { dx: 0, dy: 0 }, lineUpID: originId, }, + version: lineup.originVersion ?? 1, }, landing: { id: landingId, @@ -72,6 +79,7 @@ export function lineupPayload(id: string, lineup: TestLineup = {}) { position: lineup.landingPosition ?? { dx: 0, dy: 0 }, lineUpID: landingId, }, + version: lineup.landingVersion ?? 1, }, }, }; From 02adca37de0b2b204d8fe22493417b453bfd07d5 Mon Sep 17 00:00:00 2001 From: Dara Adedeji Date: Thu, 1 Oct 2026 23:25:40 -0400 Subject: [PATCH 04/14] Heal shared lineup spots to the drawn copy and keep old outbox work Live sync compared lineup rows in their drawn form, so a fan-out patch still queued for one row looked satisfied once the others landed, and its record was dropped; deleting the lineup holding the drawn copy then showed the stale one. Desired rows now carry each spot's drawn value and version (unchanged keeps the drawn version, a change goes one past it, a new spot starts at 1), versions taken from the rows the canvas was hydrated from, and are compared with the rows as stored. Any row holding another copy is patched until it really holds the drawn one, and a page is healed as soon as it is drawn. Versions never reach the canvas, Hive or .ica. Outbox records holding protocol 4 lineup ops are put in attention when the outbox loads, whatever state an older build left them in, so reconciling the canvas never removes them. Keep mine after a teammate's delete sends an add over the tombstone with the user's payload; a change it cannot bring back says so. Co-Authored-By: Claude Opus 5.5 (1M context) --- lib/collab/cloud_lineup_rows.dart | 210 ++- lib/collab/cloud_sync_error_message.dart | 9 + lib/collab/collab_models.dart | 12 + lib/collab/convex_payload_codecs.dart | 46 + lib/collab/generated/convex_models.dart | 150 +- lib/collab/generated/icarus_convex_api.dart | 6 + .../collab/active_page_live_sync_models.dart | 4 - .../active_page_live_sync_provider.dart | 63 +- .../collab/strategy_op_queue_provider.dart | 163 +- .../strategy_page_session_provider.dart | 14 + lib/strategy/strategy_page_source.dart | 8 +- test/cloud_lineup_rows_test.dart | 305 +++- test/cloud_payload_upgrade_test.dart | 1 - .../collab/cloud_sync_error_message_test.dart | 22 + test/collab_sync_models_test.dart | 8 +- test/global_strategy_outbox_test.dart | 4 +- test/remote_page_merge_test.dart | 1 - test/strategy_cloud_migration_test.dart | 11 +- test/strategy_op_queue_provider_test.dart | 383 ++++- test/strategy_page_session_provider_test.dart | 1318 +++++++++++++---- .../runtime/lib/workload.dart | 2 + 21 files changed, 2208 insertions(+), 532 deletions(-) diff --git a/lib/collab/cloud_lineup_rows.dart b/lib/collab/cloud_lineup_rows.dart index ae8a50f2..d0140252 100644 --- a/lib/collab/cloud_lineup_rows.dart +++ b/lib/collab/cloud_lineup_rows.dart @@ -1,3 +1,6 @@ +import 'dart:convert'; + +import 'package:icarus/collab/canonical_json.dart'; import 'package:icarus/collab/collab_models.dart'; import 'package:icarus/const/line_provider.dart'; @@ -5,57 +8,111 @@ import 'package:icarus/const/line_provider.dart'; /// images, and whole copies of its origin and landing. /// /// Lineups that share a spot (several lineups from one origin, or into one -/// landing) each carry their own copy of it, under the spot's id. The server -/// never joins rows, so every row can be drawn on its own. The client draws -/// the copies of one id as one spot again ([lineUpGraphFromCloudRows]), and -/// changing a shared spot changes every row that carries it -/// ([cloudLineupRows]). +/// landing) each carry their own copy of it, under the spot's id, with the +/// spot's version. The server never joins rows, so every row can be drawn on +/// its own. The client draws the copies of one id as one spot again +/// ([lineUpGraphFromCloudRows]) and writes every row of a spot with the copy +/// it draws ([cloudLineupRows]), so copies that drifted apart (a change that +/// reached only some of the rows) come back together. +/// +/// Versions live only here, in the cloud rows: the canvas, Hive and .ica +/// files never see them. class CloudLineupRow { - const CloudLineupRow({ - required this.publicId, - required this.payload, - this.revision = 0, - }); + const CloudLineupRow({required this.publicId, required this.payload}); CloudLineupRow.remote(RemoteLineup lineup) - : this( - publicId: lineup.publicId, - payload: lineup.payload, - revision: lineup.revision, - ); + : this(publicId: lineup.publicId, payload: lineup.payload); /// The lineup's id, as on the canvas and in `payload.data.id`. final String publicId; final CloudPayload payload; - - /// The row's server revision, which picks the copy drawn when the copies - /// of a shared spot disagree. Zero for a row the server does not have. - final int revision; } -/// The revision a lineup still waiting to be sent ranks at: above any the -/// server holds, so the user's own change to a shared spot is the copy drawn -/// until it lands. -const pendingCloudLineupRevision = 1 << 52; +/// A page's lineups as cloud rows describe them, with the version of each +/// spot drawn. +class CloudLineups { + const CloudLineups({ + required this.graph, + this.originVersions = const {}, + this.landingVersions = const {}, + }); + + static const empty = CloudLineups(graph: LineUpGraph.empty); + + final LineUpGraph graph; + + /// The version of each origin and landing drawn, by id. + final Map originVersions; + final Map landingVersions; +} /// The rows that store [graph]: one per link, in link order, each carrying /// its origin and landing as they are now. A link whose origin or landing is /// missing draws nothing on the canvas, so it has no row either. -List cloudLineupRows(LineUpGraph graph) { +/// +/// [drawn] is what the canvas was drawn from. A spot keeps the version it +/// was drawn at while it is unchanged, and goes one past it once changed, +/// so the change outranks every copy the rows hold. A spot [drawn] lacks +/// starts at version 1. +List cloudLineupRows( + LineUpGraph graph, { + CloudLineups drawn = CloudLineups.empty, +}) { + final drawnOrigins = { + for (final origin in drawn.graph.origins) origin.id: origin, + }; + final drawnLandings = { + for (final landing in drawn.graph.landings) landing.id: landing, + }; + int versionOf( + String id, + Map now, + Map? before, + Map versions, + ) { + final version = versions[id]; + if (version == null || before == null) return 1; + return _sameJson(now, before) ? version : version + 1; + } + final origins = {for (final origin in graph.origins) origin.id: origin}; final landings = {for (final landing in graph.landings) landing.id: landing}; + final originJson = >{ + for (final origin in graph.origins) + origin.id: { + ...origin.toJson(), + 'version': versionOf( + origin.id, + origin.toJson(), + drawnOrigins[origin.id]?.toJson(), + drawn.originVersions, + ), + }, + }; + final landingJson = >{ + for (final landing in graph.landings) + landing.id: { + ...landing.toJson(), + 'version': versionOf( + landing.id, + landing.toJson(), + drawnLandings[landing.id]?.toJson(), + drawn.landingVersions, + ), + }, + }; return [ for (final link in graph.links) - if ((origins[link.originId], landings[link.landingId]) - case (final origin?, final landing?)) + if (origins.containsKey(link.originId) && + landings.containsKey(link.landingId)) CloudLineupRow( publicId: link.id, payload: cloudLineupPayload({ ...link.toJson() ..remove('originId') ..remove('landingId'), - 'origin': origin.toJson(), - 'landing': landing.toJson(), + 'origin': originJson[link.originId], + 'landing': landingJson[link.landingId], }), ), ]; @@ -64,36 +121,45 @@ List cloudLineupRows(LineUpGraph graph) { /// A page's lineups as its live [rows] describe them. /// /// Links come from the rows, in their order. Origins and landings are the -/// rows' copies, one per id, in the order the rows first carry them. Copies -/// of one spot disagree only after teammates changed it at the same time; -/// every client then draws the copy from the row with the highest revision, -/// a tie going to the greatest publicId, so all of them show the same spot. +/// rows' copies, one per id, in the order the rows first carry them. When +/// copies of a spot disagree, every client draws the one with the highest +/// version, a tie going to the row with the greatest lineup id. /// /// Throws a [FormatException] naming the row when one cannot be read. -LineUpGraph lineUpGraphFromCloudRows(Iterable rows) { - final origins = {}; - final landings = {}; +CloudLineups lineUpGraphFromCloudRows(Iterable rows) { + final origins = {}; + final landings = {}; final links = []; - bool outranks(CloudLineupRow row, CloudLineupRow? drawn) => + bool outranks(int version, String rowId, (Object, int, String)? drawn) => drawn == null || - row.revision > drawn.revision || - (row.revision == drawn.revision && - row.publicId.compareTo(drawn.publicId) > 0); + version > drawn.$2 || + (version == drawn.$2 && rowId.compareTo(drawn.$3) > 0); for (final row in rows) { - final (link, origin, landing) = _readLineupRow(row); + final (link, origin, originVersion, landing, landingVersion) = + _readLineupRow(row); links.add(link); - if (outranks(row, origins[origin.id]?.$2)) { - origins[origin.id] = (origin, row); + if (outranks(originVersion, row.publicId, origins[origin.id])) { + origins[origin.id] = (origin, originVersion, row.publicId); } - if (outranks(row, landings[landing.id]?.$2)) { - landings[landing.id] = (landing, row); + if (outranks(landingVersion, row.publicId, landings[landing.id])) { + landings[landing.id] = (landing, landingVersion, row.publicId); } } - return LineUpGraph( - origins: [for (final (origin, _) in origins.values) origin], - landings: [for (final (landing, _) in landings.values) landing], - links: links, + return CloudLineups( + graph: LineUpGraph( + origins: [for (final (origin, _, _) in origins.values) origin], + landings: [for (final (landing, _, _) in landings.values) landing], + links: links, + ), + originVersions: { + for (final MapEntry(:key, value: (_, version, _)) in origins.entries) + key: version, + }, + landingVersions: { + for (final MapEntry(:key, value: (_, version, _)) in landings.entries) + key: version, + }, ); } @@ -103,25 +169,12 @@ LineUpGraph lineUpGraphFromRemoteLineups(Iterable lineups) { return lineUpGraphFromCloudRows([ for (final lineup in lineups) if (!lineup.deleted) CloudLineupRow.remote(lineup), - ]); -} - -/// The payloads of [rows] as the canvas draws them, by publicId: each row -/// rebuilt from [lineUpGraphFromCloudRows], so a row whose copy of a shared -/// spot is not the one drawn carries the drawn copy. Comparing these with -/// the canvas never mistakes the copy hydration chose for a local edit. -Map drawnCloudLineupPayloads( - Iterable rows, -) { - return { - for (final row in cloudLineupRows(lineUpGraphFromCloudRows(rows))) - row.publicId: row.payload, - }; + ]).graph; } /// Whether [op] is a lineup change in the cloud format before one row per /// lineup: an origin, landing or link row keyed `:`. An outbox -/// written by such a build can still hold one; no server takes it. +/// written by such a build can still hold one; it is never sent. bool isRetiredCloudLineupOp(StrategyOp op) { if (op.entityType != StrategyOpEntityType.lineup) return false; final payload = op.payload; @@ -131,15 +184,19 @@ bool isRetiredCloudLineupOp(StrategyOp op) { final _retiredLineupKey = RegExp(r'^lineup(Origin|Landing|Link):'); -(LineUpLink, LineUpOrigin, LineUpLanding) _readLineupRow(CloudLineupRow row) { +(LineUpLink, LineUpOrigin, int, LineUpLanding, int) _readLineupRow( + CloudLineupRow row, +) { try { final kind = row.payload['kind']; if (kind != cloudLineupPayloadKind) { throw FormatException('unknown lineup kind $kind'); } final data = cloudPayloadData(row.payload); - final origin = LineUpOrigin.fromJson(_object(data['origin'], 'origin')); - final landing = LineUpLanding.fromJson(_object(data['landing'], 'landing')); + final originJson = _object(data['origin'], 'origin'); + final landingJson = _object(data['landing'], 'landing'); + final origin = LineUpOrigin.fromJson(originJson); + final landing = LineUpLanding.fromJson(landingJson); final link = LineUpLink.fromJson({ ...data, 'originId': origin.id, @@ -148,7 +205,13 @@ final _retiredLineupKey = RegExp(r'^lineup(Origin|Landing|Link):'); if (link.id != row.publicId) { throw FormatException('it holds lineup ${link.id}'); } - return (link, origin, landing); + return ( + link, + origin, + _version(originJson, 'origin'), + landing, + _version(landingJson, 'landing'), + ); } catch (error, stackTrace) { Error.throwWithStackTrace( FormatException('Cloud lineup ${row.publicId} could not be read: ' @@ -162,3 +225,16 @@ Map _object(Object? value, String name) { if (value is! Map) throw FormatException('it has no $name'); return Map.from(value); } + +int _version(Map end, String name) { + final version = end['version']; + if (version is! num || version != version.roundToDouble() || version < 1) { + throw FormatException('its $name has no version'); + } + return version.toInt(); +} + +bool _sameJson(Map left, Map right) { + return canonicalCloudJsonEncode(jsonDecode(jsonEncode(left))) == + canonicalCloudJsonEncode(jsonDecode(jsonEncode(right))); +} diff --git a/lib/collab/cloud_sync_error_message.dart b/lib/collab/cloud_sync_error_message.dart index e42ae0b4..733118b7 100644 --- a/lib/collab/cloud_sync_error_message.dart +++ b/lib/collab/cloud_sync_error_message.dart @@ -33,6 +33,7 @@ bool isSpecificAttentionReason(String error) { return lower.contains('too large for cloud sync') || lower.contains(lineupPageMismatchMessage.toLowerCase()) || lower.contains(retiredLineupOpMessage.toLowerCase()) || + lower.contains(teammateDeletedMessage.toLowerCase()) || lower.contains(pageDeletedMessage.toLowerCase()); } @@ -133,6 +134,14 @@ String friendlyCloudSyncError(String raw) { 'saved to the cloud. It remains on this device; Use cloud removes ' 'it here.'; } + if (lower.contains(teammateDeletedCannotRestoreMessage.toLowerCase())) { + return 'A teammate deleted what this change edits, and this change ' + 'cannot bring it back. Use cloud to let it go.'; + } + if (lower.contains(teammateDeletedMessage.toLowerCase())) { + return 'A teammate deleted what you changed. Keep mine brings it back ' + 'as you have it; Use cloud lets it go.'; + } if (lower.contains(retiredLineupOpMessage.toLowerCase())) { return 'A lineup change saved by an older version of Icarus cannot be ' 'sent to the cloud, so it is not in this strategy. Redo it, then ' diff --git a/lib/collab/collab_models.dart b/lib/collab/collab_models.dart index 7d1dcfac..dbcd9fb8 100644 --- a/lib/collab/collab_models.dart +++ b/lib/collab/collab_models.dart @@ -31,6 +31,17 @@ const retiredLineupOpMessage = 'This lineup change was saved in an older cloud format and cannot be ' 'sent'; +/// Why saved work waits when the server refused it because a teammate +/// deleted what it edits (rejection reason `deleted`). Keep mine brings the +/// item back as the user has it. +const teammateDeletedMessage = 'A teammate deleted what this change edits'; + +/// Why such work stays after Keep mine: a reorder, say, holds nothing to +/// bring the item back with. +const teammateDeletedCannotRestoreMessage = + 'A teammate deleted what this change edits, and this change cannot ' + 'bring it back'; + /// The server's message when it refuses a change to a page in its trash /// (PAGE_DELETED). Such a change lands if the page is restored and it is /// sent again. @@ -1034,6 +1045,7 @@ class PendingOp { enum OpRejectionReason { alreadyExists('already_exists'), + deleted('deleted'), elementStrategyMismatch('element_strategy_mismatch'), lineupStrategyMismatch('lineup_strategy_mismatch'), missingExpectedRevision('missing_expected_revision'), diff --git a/lib/collab/convex_payload_codecs.dart b/lib/collab/convex_payload_codecs.dart index ba9474b2..53997697 100644 --- a/lib/collab/convex_payload_codecs.dart +++ b/lib/collab/convex_payload_codecs.dart @@ -121,3 +121,49 @@ final class LineupConvexCodec implements ConvexPayloadCodec { @override ConvexValue encode(CloudPayload value) => _encodePayload(value, 'lineup'); } + +// The origin, landing and link rows of protocol 4. The server still takes +// them as lineup op arguments, so an old client reaches its protocol gate +// instead of failing argument validation, and refuses them in the handler +// (see lineupOpPayloadValidator in convex/lib/payloadValidators.ts). This +// build never sends them: an outbox record holding one waits in attention +// (see isRetiredCloudLineupOp). + +@ConvexPayload('lineupOrigin') +final class LineupOriginConvexCodec + implements ConvexPayloadCodec { + const LineupOriginConvexCodec(); + + @override + CloudPayload decode(ConvexValue value) => + _decodePayload(value, 'lineupOrigin'); + + @override + ConvexValue encode(CloudPayload value) => + _encodePayload(value, 'lineupOrigin'); +} + +@ConvexPayload('lineupLanding') +final class LineupLandingConvexCodec + implements ConvexPayloadCodec { + const LineupLandingConvexCodec(); + + @override + CloudPayload decode(ConvexValue value) => + _decodePayload(value, 'lineupLanding'); + + @override + ConvexValue encode(CloudPayload value) => + _encodePayload(value, 'lineupLanding'); +} + +@ConvexPayload('lineupLink') +final class LineupLinkConvexCodec implements ConvexPayloadCodec { + const LineupLinkConvexCodec(); + + @override + CloudPayload decode(ConvexValue value) => _decodePayload(value, 'lineupLink'); + + @override + ConvexValue encode(CloudPayload value) => _encodePayload(value, 'lineupLink'); +} diff --git a/lib/collab/generated/convex_models.dart b/lib/collab/generated/convex_models.dart index cc10f41e..dbc737f8 100644 --- a/lib/collab/generated/convex_models.dart +++ b/lib/collab/generated/convex_models.dart @@ -284,6 +284,7 @@ enum InvitesCreateArgsRole { enum OpsApplyBatchResultResultsItemRejectedReason { alreadyExists('already_exists'), + deleted('deleted'), elementStrategyMismatch('element_strategy_mismatch'), lineupStrategyMismatch('lineup_strategy_mismatch'), missingExpectedRevision('missing_expected_revision'), @@ -1690,10 +1691,8 @@ final class OpsApplyBatchArgsOpsItemLineupAdd extends OpsApplyBatchArgsOpsItem { object.value['pagePublicId'] ?? _missing(path, 'pagePublicId'), '$path.pagePublicId', ), - payload: _decodePayload( - () => const LineupConvexCodec().decode( - object.value['payload'] ?? _missing(path, 'payload'), - ), + payload: _decodeOpsApplyBatchArgsOpsItemLineupAddPayload( + object.value['payload'] ?? _missing(path, 'payload'), '$path.payload', ), sortIndex: _decodeNumber( @@ -1715,8 +1714,8 @@ final class OpsApplyBatchArgsOpsItemLineupAdd extends OpsApplyBatchArgsOpsItem { 'lineupPublicId': ConvexString(lineupPublicId), 'opId': ConvexString(opId), 'pagePublicId': ConvexString(pagePublicId), - 'payload': _encodePayload( - () => const LineupConvexCodec().encode(payload), + 'payload': _encodeOpsApplyBatchArgsOpsItemLineupAddPayload( + payload, '$path.payload', ), 'sortIndex': _encodeNumber(sortIndex, '$path.sortIndex'), @@ -1840,9 +1839,8 @@ final class OpsApplyBatchArgsOpsItemLineupPatch : const ConvexOptional.absent(), payload: object.value.containsKey('payload') ? ConvexOptional.present( - _decodePayload( - () => - const LineupConvexCodec().decode(object.value['payload']!), + _decodeOpsApplyBatchArgsOpsItemLineupPatchPayload( + object.value['payload']!, '$path.payload', ), ) @@ -1868,8 +1866,8 @@ final class OpsApplyBatchArgsOpsItemLineupPatch if (pagePublicId.isPresent) 'pagePublicId': ConvexString(pagePublicId.value), if (payload.isPresent) - 'payload': _encodePayload( - () => const LineupConvexCodec().encode(payload.value), + 'payload': _encodeOpsApplyBatchArgsOpsItemLineupPatchPayload( + payload.value, '$path.payload', ), if (sortIndex.isPresent) @@ -4608,6 +4606,130 @@ ConvexValue _encodeOpsApplyBatchArgsOpsItemElementPatchPayload( }; } +CloudPayload _decodeOpsApplyBatchArgsOpsItemLineupAddPayload( + ConvexValue value, + String path, +) { + final object = _decodeObject(value, path); + final tag = _decodeString( + object.value['kind'] ?? _missing(path, 'kind'), + '$path.kind', + ); + return switch (tag) { + 'lineup' => _decodePayload( + () => const LineupConvexCodec().decode(value), + path, + ), + 'lineupOrigin' => _decodePayload( + () => const LineupOriginConvexCodec().decode(value), + path, + ), + 'lineupLanding' => _decodePayload( + () => const LineupLandingConvexCodec().decode(value), + path, + ), + 'lineupLink' => _decodePayload( + () => const LineupLinkConvexCodec().decode(value), + path, + ), + _ => throw ConvexDecodingException( + '$path.kind', + 'unknown payload tag $tag', + ), + }; +} + +ConvexValue _encodeOpsApplyBatchArgsOpsItemLineupAddPayload( + CloudPayload value, + String path, +) { + final tag = value['kind']; + return switch (tag) { + 'lineup' => _encodePayload( + () => const LineupConvexCodec().encode(value), + path, + ), + 'lineupOrigin' => _encodePayload( + () => const LineupOriginConvexCodec().encode(value), + path, + ), + 'lineupLanding' => _encodePayload( + () => const LineupLandingConvexCodec().encode(value), + path, + ), + 'lineupLink' => _encodePayload( + () => const LineupLinkConvexCodec().encode(value), + path, + ), + _ => throw ConvexEncodingException( + '$path.kind', + 'unknown payload tag $tag', + ), + }; +} + +CloudPayload _decodeOpsApplyBatchArgsOpsItemLineupPatchPayload( + ConvexValue value, + String path, +) { + final object = _decodeObject(value, path); + final tag = _decodeString( + object.value['kind'] ?? _missing(path, 'kind'), + '$path.kind', + ); + return switch (tag) { + 'lineup' => _decodePayload( + () => const LineupConvexCodec().decode(value), + path, + ), + 'lineupOrigin' => _decodePayload( + () => const LineupOriginConvexCodec().decode(value), + path, + ), + 'lineupLanding' => _decodePayload( + () => const LineupLandingConvexCodec().decode(value), + path, + ), + 'lineupLink' => _decodePayload( + () => const LineupLinkConvexCodec().decode(value), + path, + ), + _ => throw ConvexDecodingException( + '$path.kind', + 'unknown payload tag $tag', + ), + }; +} + +ConvexValue _encodeOpsApplyBatchArgsOpsItemLineupPatchPayload( + CloudPayload value, + String path, +) { + final tag = value['kind']; + return switch (tag) { + 'lineup' => _encodePayload( + () => const LineupConvexCodec().encode(value), + path, + ), + 'lineupOrigin' => _encodePayload( + () => const LineupOriginConvexCodec().encode(value), + path, + ), + 'lineupLanding' => _encodePayload( + () => const LineupLandingConvexCodec().encode(value), + path, + ), + 'lineupLink' => _encodePayload( + () => const LineupLinkConvexCodec().encode(value), + path, + ), + _ => throw ConvexEncodingException( + '$path.kind', + 'unknown payload tag $tag', + ), + }; +} + CloudPayload _decodeOpsApplyBatchResultResultsItemRejectedCurrentElementValue( ConvexValue value, String path, @@ -5270,6 +5392,8 @@ List decodeLineupsListForStrategyResult( ConvexObject encodeOpsApplyBatchArgs({ ConvexOptional accountSubject = const ConvexOptional.absent(), + ConvexOptional checkLineupEndDeletes = const ConvexOptional.absent(), + ConvexOptional checkLineupLinkEnds = const ConvexOptional.absent(), ConvexOptional checkTrashedPageDeletes = const ConvexOptional.absent(), required String clientId, required double clientProtocolVersion, @@ -5278,6 +5402,10 @@ ConvexObject encodeOpsApplyBatchArgs({ }) => ConvexObject({ if (accountSubject.isPresent) 'accountSubject': ConvexString(accountSubject.value), + if (checkLineupEndDeletes.isPresent) + 'checkLineupEndDeletes': ConvexBoolean(checkLineupEndDeletes.value), + if (checkLineupLinkEnds.isPresent) + 'checkLineupLinkEnds': ConvexBoolean(checkLineupLinkEnds.value), if (checkTrashedPageDeletes.isPresent) 'checkTrashedPageDeletes': ConvexBoolean(checkTrashedPageDeletes.value), 'clientId': ConvexString(clientId), diff --git a/lib/collab/generated/icarus_convex_api.dart b/lib/collab/generated/icarus_convex_api.dart index 1fe40a4c..a4facdd0 100644 --- a/lib/collab/generated/icarus_convex_api.dart +++ b/lib/collab/generated/icarus_convex_api.dart @@ -726,6 +726,8 @@ final class _LineupsModule implements LineupsModule { abstract interface class OpsModule { Future applyBatch({ ConvexOptional accountSubject = const ConvexOptional.absent(), + ConvexOptional checkLineupEndDeletes = const ConvexOptional.absent(), + ConvexOptional checkLineupLinkEnds = const ConvexOptional.absent(), ConvexOptional checkTrashedPageDeletes = const ConvexOptional.absent(), required String clientId, @@ -741,6 +743,8 @@ final class _OpsModule implements OpsModule { @override Future applyBatch({ ConvexOptional accountSubject = const ConvexOptional.absent(), + ConvexOptional checkLineupEndDeletes = const ConvexOptional.absent(), + ConvexOptional checkLineupLinkEnds = const ConvexOptional.absent(), ConvexOptional checkTrashedPageDeletes = const ConvexOptional.absent(), required String clientId, @@ -750,6 +754,8 @@ final class _OpsModule implements OpsModule { }) { final args = encodeOpsApplyBatchArgs( accountSubject: accountSubject, + checkLineupEndDeletes: checkLineupEndDeletes, + checkLineupLinkEnds: checkLineupLinkEnds, checkTrashedPageDeletes: checkTrashedPageDeletes, clientId: clientId, clientProtocolVersion: clientProtocolVersion, diff --git a/lib/providers/collab/active_page_live_sync_models.dart b/lib/providers/collab/active_page_live_sync_models.dart index ca7ab8de..bed2115d 100644 --- a/lib/providers/collab/active_page_live_sync_models.dart +++ b/lib/providers/collab/active_page_live_sync_models.dart @@ -190,15 +190,11 @@ class ProjectedPageLineup { required this.publicId, required this.payload, required this.sortIndex, - required this.revision, }); final String publicId; final CloudPayload payload; final int sortIndex; - - /// Ranks this lineup's copies of shared spots (see CloudLineupRow). - final int revision; } class ActivePageProjectedState { diff --git a/lib/providers/collab/active_page_live_sync_provider.dart b/lib/providers/collab/active_page_live_sync_provider.dart index d3a17092..6c899813 100644 --- a/lib/providers/collab/active_page_live_sync_provider.dart +++ b/lib/providers/collab/active_page_live_sync_provider.dart @@ -707,7 +707,6 @@ class ActivePageLiveSyncNotifier extends Notifier { publicId: lineup.publicId, payload: lineup.payload, sortIndex: lineup.sortIndex, - revision: lineup.revision, ), }; @@ -777,9 +776,6 @@ class ActivePageLiveSyncNotifier extends Notifier { publicId: lineupId, payload: Map.from(overlay.desiredPayload! as Map), sortIndex: overlay.desiredSortIndex ?? 0, - // The user's own change to a shared spot is the copy drawn - // until it lands. - revision: pendingCloudLineupRevision, ); continue; } @@ -860,21 +856,13 @@ class ActivePageLiveSyncNotifier extends Notifier { ); } - // A live lineup compares as the canvas draws it: carrying the drawn copy - // of each spot it shares, so the copy hydration chose never reads as an - // edit to the rows whose copy lost. - final lineups = - snapshot.lineupsByPage[page.publicId] ?? const []; - final drawnLineups = drawnCloudLineupPayloads([ - for (final lineup in lineups) - if (!lineup.deleted) CloudLineupRow.remote(lineup), - ]); - for (final lineup in lineups) { + for (final lineup + in (snapshot.lineupsByPage[page.publicId] ?? const [])) { final key = EntitySyncKey.lineup(page.publicId, lineup.publicId); entities[key] = _NormalizedEntity( key: key, overlayEntityType: ActivePageOverlayEntityType.lineup, - payload: drawnLineups[lineup.publicId] ?? lineup.payload, + payload: lineup.payload, sortIndex: lineup.sortIndex, revision: lineup.revision, deleted: lineup.deleted, @@ -973,10 +961,15 @@ class ActivePageLiveSyncNotifier extends Notifier { ); } - // One row per lineup, carrying its origin and landing as they are now, so - // a change to a shared spot changes every lineup that uses it. + // One row per lineup, carrying its origin and landing as the canvas draws + // them. A change to a shared spot therefore changes every lineup on it, + // and a row whose stored copy of a spot is not the drawn one is written + // with the drawn one: compared with the rows as stored, it differs. final freshLineupSortIndex = freshSortIndexes(EntitySyncKeyKind.lineup); - for (final row in cloudLineupRows(ref.read(lineUpProvider).graph)) { + for (final row in cloudLineupRows( + ref.read(lineUpProvider).graph, + drawn: _drawnLineups(pageId), + )) { final key = EntitySyncKey.lineup(pageId, row.publicId); entities[key] = _NormalizedEntity( key: key, @@ -991,6 +984,40 @@ class ActivePageLiveSyncNotifier extends Notifier { return entities; } + /// Whether a lineup row of [pageId] holds a copy of a spot other than the + /// one the canvas draws (a change reached only some rows of the spot, or a + /// conflict was settled for one row), or other work the canvas holds for + /// it. The next sync writes each such row as the canvas has it. + bool lineupsNeedHealing(String pageId) { + if (state.hydratedPageId != pageId) return false; + return _normalizedLocalEntities(pageId).entries.any( + (entry) => + entry.key.kind == EntitySyncKeyKind.lineup && + !_entitiesEquivalent( + entry.value, + _hydratedBaseByEntityKey[entry.key], + ), + ); + } + + /// [pageId]'s lineups as the canvas was drawn from them: the live lineup + /// rows it was hydrated from, as accepted since. Their spot versions are + /// what a change on the canvas builds on. + CloudLineups _drawnLineups(String pageId) { + return lineUpGraphFromCloudRows([ + for (final MapEntry(:key, value: base) + in _hydratedBaseByEntityKey.entries) + if (key.pageId == pageId && + key.kind == EntitySyncKeyKind.lineup && + key.entityId != null && + !base.deleted) + CloudLineupRow( + publicId: key.entityId!, + payload: Map.from(base.payload as Map), + ), + ]); + } + List<_CollabElementEnvelope> _collectLocalElementEnvelopes() { final envelopes = <_CollabElementEnvelope>[]; diff --git a/lib/providers/collab/strategy_op_queue_provider.dart b/lib/providers/collab/strategy_op_queue_provider.dart index 7487e479..1908e0d1 100644 --- a/lib/providers/collab/strategy_op_queue_provider.dart +++ b/lib/providers/collab/strategy_op_queue_provider.dart @@ -239,7 +239,7 @@ class StrategyOpQueueNotifier extends Notifier { final loaded = _store.load(); _recordsByStorageKey = { for (final record in loaded.records) - record.storageKey: _resumedAfterUpgrade(record), + record.storageKey: _retiredAsAttention(_resumedAfterUpgrade(record)), }; ref.onDispose(() { _isDisposed = true; @@ -289,6 +289,24 @@ class StrategyOpQueueNotifier extends Notifier { ); } + /// [record] waiting in attention when it holds a lineup change in the + /// cloud format before one row per lineup, whatever state an older build + /// left it in (queued, paused, refused for another reason). It can never + /// be sent; in attention it is shown, with its reason, and nothing but the + /// user's discard removes it: reconciling the canvas's work never touches + /// attention. Like [_resumedAfterUpgrade], only the copy in memory changes. + static DurableOutboxRecord _retiredAsAttention(DurableOutboxRecord record) { + if (!isRetiredCloudLineupOp(record.pending.op) || + (record.status == DurableOutboxStatus.attention && + record.lastError == retiredLineupOpMessage)) { + return record; + } + return record.copyWith( + status: DurableOutboxStatus.attention, + lastError: retiredLineupOpMessage, + ); + } + /// [record] as this build sends it. Work a build the server refused kept /// failing through no fault of its own, and may have been paused for it; /// it goes back in the queue with no failures counted. Only the copy in @@ -992,13 +1010,24 @@ class StrategyOpQueueNotifier extends Notifier { (retryOp is ElementAddOp || retryOp is LineupAddOp) && (record?.lastError == 'missing_expected_revision' || record?.lastError == 'revision_mismatch'); - final rebasedOp = retriesAsSent - ? retryOp.withOpId(const Uuid().v4()) - : _rebaseRejectedOp( - retryOp, - retryRevision!, - preserveAdd: isTombstoneRestore, - ); + // A teammate deleted what this change edits. Keeping mine brings + // it back as the user has it: an add over the tombstone. + final restoresDeleted = + record?.lastError == OpRejectionReason.deleted.wireName; + final StrategyOp rebasedOp; + if (retriesAsSent) { + rebasedOp = retryOp.withOpId(const Uuid().v4()); + } else if (restoresDeleted) { + final restore = _restoreOp(retryOp, retryRevision!); + if (restore == null) continue; + rebasedOp = restore; + } else { + rebasedOp = _rebaseRejectedOp( + retryOp, + retryRevision!, + preserveAdd: isTombstoneRestore, + ); + } _keepCanvasWritten(retryOp, rebasedOp); final pending = PendingOp( op: rebasedOp, @@ -1046,6 +1075,13 @@ class StrategyOpQueueNotifier extends Notifier { final onlyRetired = attention.values.every( (intent) => isRetiredCloudLineupOp(intent.pending.op), ); + // Changes a teammate's delete refused that hold nothing to bring + // the item back with. + final cannotRestore = attention.keys.any( + (key) => + _recordForActiveKey(key)?.lastError == + OpRejectionReason.deleted.wireName, + ); state = state.copyWith( lastError: onlyRetired ? _loadedAttentionMessage( @@ -1053,9 +1089,11 @@ class StrategyOpQueueNotifier extends Notifier { paused: state.pausedByEntityKey, attention: attention, ) - : 'Some retained cloud work cannot be retried ' - 'automatically because the server has no matching ' - 'revision.', + : cannotRestore + ? teammateDeletedCannotRestoreMessage + : 'Some retained cloud work cannot be retried ' + 'automatically because the server has no matching ' + 'revision.', ); return; } @@ -1376,35 +1414,15 @@ class StrategyOpQueueNotifier extends Notifier { _retryTimer?.cancel(); _retryTimer = null; _offlineRetryCount = 0; - // A lineup change in the old cloud format has no shape the server - // takes, so it is refused here, as the server would refuse it, and - // waits in attention with the rest of the batch sent. - final sendable = [ - for (final record in batch) - if (!isRetiredCloudLineupOp(record.pending.op)) record, - ]; - final acks = [ - if (sendable.isNotEmpty) - ...await _repo.applyBatch( - strategyPublicId: strategyPublicId, - clientId: batchClientId, - ops: sendable - .map((record) => record.pending.op) - .toList(growable: false), - // The transport can deliver this after a later sign-in (it - // resends pending requests on reconnect), so the server checks - // the batch's own account, not whoever is signed in then. - accountSubject: batch.first.accountId, - ), - for (final record in batch) - if (isRetiredCloudLineupOp(record.pending.op)) - FailedOpAck( - opId: record.pending.op.opId, - code: 'INVALID_LINEUP_PAYLOAD_KIND', - rawCode: 'INVALID_LINEUP_PAYLOAD_KIND', - message: retiredLineupOpMessage, - ), - ]; + final acks = await _repo.applyBatch( + strategyPublicId: strategyPublicId, + clientId: batchClientId, + ops: batch.map((record) => record.pending.op).toList(growable: false), + // The transport can deliver this after a later sign-in (it resends + // pending requests on reconnect), so the server checks the batch's + // own account, not whoever is signed in then. + accountSubject: batch.first.accountId, + ); await _applyAcksForRecords(batch, acks); batchSucceeded = true; } catch (error, stackTrace) { @@ -2577,6 +2595,57 @@ class StrategyOpQueueNotifier extends Notifier { }); } + /// [op] as an add bringing its element or lineup back over the tombstone + /// at [revision], with the payload and place the user has. Null for a + /// change that does not hold them (a reorder, a delete); live sync's + /// patches always do. + static StrategyOp? _restoreOp(StrategyOp op, int revision) { + final opId = const Uuid().v4(); + return switch (op) { + ElementAddOp( + :final elementPublicId, + :final pagePublicId, + :final payload, + :final sortIndex, + ) || + ElementPatchOp( + :final elementPublicId, + pagePublicId: final pagePublicId?, + payload: final payload?, + sortIndex: final sortIndex?, + ) => + ElementAddOp( + opId: opId, + elementPublicId: elementPublicId, + pagePublicId: pagePublicId, + payload: payload, + sortIndex: sortIndex, + expectedElementRevision: revision, + ), + LineupAddOp( + :final lineupPublicId, + :final pagePublicId, + :final payload, + :final sortIndex, + ) || + LineupPatchOp( + :final lineupPublicId, + pagePublicId: final pagePublicId?, + payload: final payload?, + sortIndex: final sortIndex?, + ) => + LineupAddOp( + opId: opId, + lineupPublicId: lineupPublicId, + pagePublicId: pagePublicId, + payload: payload, + sortIndex: sortIndex, + expectedLineupRevision: revision, + ), + _ => null, + }; + } + StrategyOp _rebaseRejectedOp( StrategyOp op, int revision, { @@ -2783,14 +2852,14 @@ class StrategyOpQueueNotifier extends Notifier { var hasOtherWork = false; for (final entry in attention.entries) { final record = recordFor(entry.key); - // An old-format lineup change keeps its reason whatever an older - // build recorded for it. - final reason = isRetiredCloudLineupOp(entry.value.pending.op) - ? retiredLineupOpMessage - : record?.pending.op.opId == entry.value.pending.op.opId - ? record?.lastError - : null; + final lastError = record?.pending.op.opId == entry.value.pending.op.opId + ? record?.lastError + : null; + final reason = lastError == OpRejectionReason.deleted.wireName + ? teammateDeletedMessage + : lastError; if (reason == lineupPageMismatchMessage || + reason == teammateDeletedMessage || reason == retiredLineupOpMessage || reason == pageDeletedMessage) { specificReason ??= reason; diff --git a/lib/providers/strategy_page_session_provider.dart b/lib/providers/strategy_page_session_provider.dart index 084108c1..455b8d4b 100644 --- a/lib/providers/strategy_page_session_provider.dart +++ b/lib/providers/strategy_page_session_provider.dart @@ -840,6 +840,7 @@ class StrategyPageSessionNotifier extends Notifier { snapshot: loadedRemoteSnapshot, ); _lastAppliedRemoteSnapshot = loadedRemoteSnapshot; + _healLineupsOf(pageData.pageId); } _updateHydrationBookkeeping( pageData.pageId, @@ -1308,6 +1309,7 @@ class StrategyPageSessionNotifier extends Notifier { keepBaseFor: heldBack, ); _lastAppliedRemoteSnapshot = snapshot; + _healLineupsOf(pageId); _updateHydrationBookkeeping( pageId, hydrationKey: _buildRemotePageHydrationKey(snapshot, pageId), @@ -1315,6 +1317,18 @@ class StrategyPageSessionNotifier extends Notifier { if (heldBack.isNotEmpty) _remoteChangeWaitsForEditor = true; } + /// Sends the drawn copy of each shared spot to the lineup rows of + /// [pageId] that hold another, as soon as the page is drawn: until then a + /// lineup whose row still holds the old copy would show it to anyone who + /// opens the page after the lineup drawing the new copy is deleted. + void _healLineupsOf(String pageId) { + if (ref.read(activePageLiveSyncProvider.notifier).lineupsNeedHealing( + pageId, + )) { + unawaited(ref.read(strategyProvider.notifier).notifyCloudMutation()); + } + } + String? _resolveHydrationTargetPage(RemoteEditorSnapshot snapshot) { if (snapshot.pages.isEmpty) { return null; diff --git a/lib/strategy/strategy_page_source.dart b/lib/strategy/strategy_page_source.dart index 6f700dab..2567f620 100644 --- a/lib/strategy/strategy_page_source.dart +++ b/lib/strategy/strategy_page_source.dart @@ -451,12 +451,8 @@ class CloudStrategyPageSource implements StrategyPageSource { final lineUpGraph = lineUpGraphFromCloudRows([ for (final lineup in projected.lineups) - CloudLineupRow( - publicId: lineup.publicId, - payload: lineup.payload, - revision: lineup.revision, - ), - ]); + CloudLineupRow(publicId: lineup.publicId, payload: lineup.payload), + ]).graph; final mapValue = Maps.mapNames.entries.firstWhere( (entry) => entry.value == snapshot.header.mapData, diff --git a/test/cloud_lineup_rows_test.dart b/test/cloud_lineup_rows_test.dart index c3810829..5dd5ad50 100644 --- a/test/cloud_lineup_rows_test.dart +++ b/test/cloud_lineup_rows_test.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:flutter_test/flutter_test.dart'; +import 'package:icarus/collab/canonical_json.dart'; import 'package:icarus/collab/cloud_lineup_rows.dart'; import 'package:icarus/collab/collab_models.dart'; import 'package:icarus/const/agents.dart'; @@ -49,9 +50,64 @@ LineUpGraph _shared() => LineUpGraph( String _json(LineUpGraph graph) => jsonEncode(graph.toJson()); -CloudLineupRow _withRevision(CloudLineupRow row, int revision) => - CloudLineupRow( - publicId: row.publicId, payload: row.payload, revision: revision); +/// [rows] as canonical JSON, to compare them exactly. +String _canonical(Iterable rows) => + canonicalCloudJsonEncode(jsonDecode(jsonEncode([ + for (final row in rows) + {'publicId': row.publicId, 'payload': row.payload}, + ]))); + +/// [json], as JSON, with a version, as a row carries a spot. +Map _versioned(Object json, int version) => { + ...jsonDecode(jsonEncode(json)) as Map, + 'version': version, + }; + +/// The [end] (`origin` or `landing`) [row] carries. +Map _end(CloudLineupRow row, String end) => + Map.from((row.payload['data'] as Map)[end] as Map); + +/// [row] with its ends' versions set to [origin] and [landing] (or removed, +/// for [_noVersion]), as another client may have written it. +CloudLineupRow _atVersions( + CloudLineupRow row, { + Object? origin, + Object? landing, +}) { + Map withVersion(String end, Object? version) { + final json = _end(row, end); + if (version == null) return json; + if (identical(version, _noVersion)) return json..remove('version'); + return json..['version'] = version; + } + + return CloudLineupRow( + publicId: row.publicId, + payload: { + ...row.payload, + 'data': { + ...Map.from(row.payload['data'] as Map), + 'origin': withVersion('origin', origin), + 'landing': withVersion('landing', landing), + }, + }, + ); +} + +const _noVersion = Object(); + +/// [_shared] with landing `shared` moved. +LineUpGraph _sharedMoved() => LineUpGraph( + origins: _shared().origins, + landings: [ + _landing('shared', position: const Offset(99, 99)), + _landing('other'), + ], + links: _shared().links, + ); + +Offset _landingAt(CloudLineups read, String id) => + read.graph.landings.firstWhere((l) => l.id == id).ability.position; void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -73,9 +129,9 @@ void main() { 'origin', 'landing', }); - expect(data['origin'], jsonDecode(jsonEncode(_origin('a').toJson()))); - expect( - data['landing'], jsonDecode(jsonEncode(_landing('shared').toJson()))); + // A spot nothing was drawn from yet starts at version 1. + expect(data['origin'], _versioned(_origin('a').toJson(), 1)); + expect(data['landing'], _versioned(_landing('shared').toJson(), 1)); expect(data['images'], [ {'id': 'img-1', 'fileExtension': '.png'}, ]); @@ -84,7 +140,29 @@ void main() { test('shared spots read back as one spot, sharing intact', () { final read = lineUpGraphFromCloudRows(cloudLineupRows(_shared())); - expect(_json(read), _json(_shared())); + expect(_json(read.graph), _json(_shared())); + expect(read.originVersions, {'a': 1, 'b': 1}); + expect(read.landingVersions, {'shared': 1, 'other': 1}); + }); + + test('fan-in and fan-out round-trip through rows drawn and written again', + () { + // Rows another client left at versions past 1. + final rows = [ + for (final row in cloudLineupRows(_shared())) + _atVersions(row, origin: 2, landing: 4), + ]; + + final read = lineUpGraphFromCloudRows(rows); + expect(_json(read.graph), _json(_shared())); + expect(read.originVersions, {'a': 2, 'b': 2}); + expect(read.landingVersions, {'shared': 4, 'other': 4}); + + // Nothing changed, so writing what was drawn gives back the same rows, + // versions and all. + final written = cloudLineupRows(read.graph, drawn: read); + expect(_canonical(written), _canonical(rows)); + expect(_json(lineUpGraphFromCloudRows(written).graph), _json(_shared())); }); test('a link missing its origin or landing has no row', () { @@ -100,64 +178,177 @@ void main() { expect(rows.map((row) => row.publicId), ['k1']); }); - test('disagreeing copies of a spot draw the highest revision, then id', () { - final moved = LineUpGraph( - origins: [_origin('a'), _origin('b')], - landings: [_landing('shared', position: const Offset(99, 99))], - links: _shared().links.take(2).toList(), - ); - final original = cloudLineupRows(_shared()); - final edited = cloudLineupRows(moved); - Offset landingOf(LineUpGraph graph) => - graph.landings.firstWhere((l) => l.id == 'shared').ability.position; - - // k2 carries the moved copy at a higher revision than k1. - var read = lineUpGraphFromCloudRows([ - _withRevision(original[0], 3), - _withRevision(edited[1], 4), + test('a spot keeps its version while unchanged and goes one past it changed', + () { + final drawn = lineUpGraphFromCloudRows([ + for (final row in cloudLineupRows(_shared())) + _atVersions(row, origin: 3, landing: 5), ]); - expect(landingOf(read), const Offset(99, 99)); + // Landing `shared` moved, lineup k1 renamed, and a new lineup from a new + // origin into a new landing. + final edited = LineUpGraph( + origins: [..._sharedMoved().origins, _origin('c')], + landings: [..._sharedMoved().landings, _landing('fresh')], + links: [ + for (final link in _sharedMoved().links) + link.id == 'k1' + ? LineUpLink( + id: 'k1', + originId: 'a', + landingId: 'shared', + name: 'A, renamed', + ) + : link, + LineUpLink(id: 'k4', originId: 'c', landingId: 'fresh'), + ], + ); - // Same rows, the other copy ahead: the order rows arrive in never - // decides. - read = lineUpGraphFromCloudRows([ - _withRevision(edited[1], 2), - _withRevision(original[0], 3), - ]); - expect(landingOf(read), const Offset(30, 40)); + final rows = cloudLineupRows(edited, drawn: drawn); - // A tie goes to the greatest publicId (k2 over k1). - read = lineUpGraphFromCloudRows([ - _withRevision(original[0], 5), - _withRevision(edited[1], 5), - ]); - expect(landingOf(read), const Offset(99, 99)); - read = lineUpGraphFromCloudRows([ - _withRevision(edited[1], 5), - _withRevision(original[0], 5), - ]); - expect(landingOf(read), const Offset(99, 99)); - // The spot keeps the place its first row gave it. - expect(read.landings.map((landing) => landing.id), ['shared']); + expect( + { + for (final row in rows) + row.publicId: ( + _end(row, 'origin')['version'], + _end(row, 'landing')['version'], + ), + }, + { + // A lineup's own fields are not its spots: renaming k1 leaves + // origin a at 3. Every row carrying the moved landing carries it + // one past the version drawn. + 'k1': (3, 6), + 'k2': (3, 6), + 'k3': (3, 5), + // Spots nothing was drawn from start at 1. + 'k4': (1, 1), + }, + ); + expect(_end(rows[1], 'landing'), _end(rows[0], 'landing')); }); - test('drawn payloads carry the copy that is drawn', () { - final moved = LineUpGraph( - origins: [_origin('a'), _origin('b')], - landings: [_landing('shared', position: const Offset(99, 99))], - links: _shared().links.take(2).toList(), - ); - final stale = _withRevision(cloudLineupRows(_shared())[0], 1); - final fresh = _withRevision(cloudLineupRows(moved)[1], 2); + test('a change that reached only some rows outranks the copies it missed', + () { + final before = [ + for (final row in cloudLineupRows(_shared())) + _atVersions(row, origin: 1, landing: 5), + ]; + final drawn = lineUpGraphFromCloudRows(before); + final moved = cloudLineupRows(_sharedMoved(), drawn: drawn); + expect(_end(moved[0], 'landing')['version'], 6); + + // Only k1's write landed. k2 still holds the old copy and, at an equal + // version, would win on its greater lineup id. + final partial = lineUpGraphFromCloudRows([moved[0], before[1], before[2]]); - final drawn = drawnCloudLineupPayloads([stale, fresh]); + expect(_landingAt(partial, 'shared'), const Offset(99, 99)); + expect(partial.landingVersions['shared'], 6); - expect(drawn.keys, ['k1', 'k2']); - expect(drawn['k2'], fresh.payload); + // Writing what is drawn brings k2's copy back in line, at the same + // version, since the drawn spot itself did not change. + final healed = cloudLineupRows(partial.graph, drawn: partial); + expect(_end(healed[1], 'landing'), _end(moved[0], 'landing')); + expect(_canonical(healed), _canonical(moved)); + }); + + test('disagreeing copies draw the highest version, then the greatest id', () { + final original = cloudLineupRows(_shared()); + final moved = cloudLineupRows(_sharedMoved()); + // Every order the rows can arrive in draws the same spot. + void expectDrawn( + List rows, + Offset position, + int version, + ) { + for (final order in [rows, rows.reversed.toList()]) { + final read = lineUpGraphFromCloudRows(order); + expect(_landingAt(read, 'shared'), position); + expect(read.landingVersions['shared'], version); + } + } + + // k2 carries the moved copy at a higher version than k1. + expectDrawn( + [ + _atVersions(original[0], landing: 3), + _atVersions(moved[1], landing: 4), + ], + const Offset(99, 99), + 4, + ); + // The original copy ahead wins though its lineup id is lower. + expectDrawn( + [ + _atVersions(original[0], landing: 3), + _atVersions(moved[1], landing: 2), + ], + const Offset(30, 40), + 3, + ); + // A tie goes to the greatest lineup id (k2 over k1). + expectDrawn( + [ + _atVersions(original[0], landing: 5), + _atVersions(moved[1], landing: 5), + ], + const Offset(99, 99), + 5, + ); + + // Each spot is ranked on its own: origin a is drawn from k3, landing + // shared from k1. + final movedOrigin = cloudLineupRows(LineUpGraph( + origins: [_origin('a', position: const Offset(77, 77)), _origin('b')], + landings: _shared().landings, + links: _shared().links, + )); + final read = lineUpGraphFromCloudRows([ + _atVersions(moved[0], origin: 1, landing: 2), + _atVersions(original[1], origin: 1, landing: 1), + _atVersions(movedOrigin[2], origin: 2, landing: 1), + ]); expect( - (drawn['k1']!['data'] as Map)['landing'], - (fresh.payload['data'] as Map)['landing'], + read.graph.origins.firstWhere((o) => o.id == 'a').agent.position, + const Offset(77, 77), ); + expect(_landingAt(read, 'shared'), const Offset(99, 99)); + expect(read.originVersions, {'a': 2, 'b': 1}); + expect(read.landingVersions, {'shared': 2, 'other': 1}); + // A spot keeps the place its first row gave it, whichever row's copy is + // drawn. + expect(read.graph.origins.map((o) => o.id), ['a', 'b']); + expect(read.graph.landings.map((l) => l.id), ['shared', 'other']); + }); + + test('an end without a whole positive version fails loudly, naming the row', + () { + final valid = cloudLineupRows(_shared()).first; + for (final end in ['origin', 'landing']) { + for (final version in [_noVersion, 0, -1, 1.5, '2', true]) { + final row = end == 'origin' + ? _atVersions(valid, origin: version) + : _atVersions(valid, landing: version); + expect( + () => lineUpGraphFromCloudRows([row]), + throwsA(isA().having( + (error) => error.message, + 'message', + allOf( + contains('Cloud lineup k1 could not be read'), + contains('its $end has no version'), + ), + )), + reason: '$end at $version', + ); + } + } + // A version the server returns as a float64 integer reads as that + // integer. + final read = lineUpGraphFromCloudRows([ + _atVersions(valid, origin: 2.0, landing: 3.0), + ]); + expect(read.originVersions, {'a': 2}); + expect(read.landingVersions, {'shared': 3}); }); test('a row of any other shape fails loudly, naming the row', () { @@ -233,7 +424,7 @@ void main() { ); } expect( - isRetiredCloudLineupOp(ElementDeleteOp( + isRetiredCloudLineupOp(const ElementDeleteOp( opId: 'op', elementPublicId: 'lineupLink:not-a-lineup', pagePublicId: 'p', diff --git a/test/cloud_payload_upgrade_test.dart b/test/cloud_payload_upgrade_test.dart index 2a5d9327..be9808b5 100644 --- a/test/cloud_payload_upgrade_test.dart +++ b/test/cloud_payload_upgrade_test.dart @@ -1,4 +1,3 @@ -import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:icarus/collab/cloud_lineup_rows.dart'; import 'package:icarus/collab/cloud_payload_upgrade.dart'; diff --git a/test/collab/cloud_sync_error_message_test.dart b/test/collab/cloud_sync_error_message_test.dart index c1d6ab11..65cfb549 100644 --- a/test/collab/cloud_sync_error_message_test.dart +++ b/test/collab/cloud_sync_error_message_test.dart @@ -30,6 +30,23 @@ void main() { expect(message, isNot(contains('LINEUP_PAGE_MISMATCH'))); }); + test('explains a change to something a teammate deleted', () { + final message = friendlyCloudSyncError(teammateDeletedMessage); + + expect(message, contains('A teammate deleted what you changed')); + expect(message, contains('Keep mine brings it back')); + expect(message, contains('Use cloud lets it go')); + }); + + test('says when Keep mine cannot bring a deleted item back', () { + final message = friendlyCloudSyncError(teammateDeletedCannotRestoreMessage); + + expect(message, contains('cannot bring it back')); + expect(message, contains('Use cloud')); + expect(message, isNot(contains('Keep mine'))); + expect(message, isNot(contains('matching revision'))); + }); + test('explains a lineup change saved in the old cloud format', () { final message = friendlyCloudSyncError(retiredLineupOpMessage); @@ -42,6 +59,11 @@ void main() { test('lineup refusals and oversized work are specific attention reasons', () { expect(isSpecificAttentionReason(lineupPageMismatchMessage), isTrue); expect(isSpecificAttentionReason(retiredLineupOpMessage), isTrue); + expect(isSpecificAttentionReason(teammateDeletedMessage), isTrue); + expect( + isSpecificAttentionReason(teammateDeletedCannotRestoreMessage), + isTrue, + ); expect(isSpecificAttentionReason(pageDeletedMessage), isTrue); expect(isSpecificAttentionReason(cloudOperationTooLargeMessage), isTrue); for (final reason in [ diff --git a/test/collab_sync_models_test.dart b/test/collab_sync_models_test.dart index 4ed1e82c..1f1aa73d 100644 --- a/test/collab_sync_models_test.dart +++ b/test/collab_sync_models_test.dart @@ -408,6 +408,8 @@ void main() { 'position': {'dx': 10, 'dy': 20}, 'lineUpID': 'origin-1', }, + // Convex returns every number as a float64. + 'version': 1.0, }, 'landing': { 'id': 'landing-1', @@ -417,13 +419,15 @@ void main() { 'position': {'dx': 30, 'dy': 40}, 'lineUpID': 'landing-1', }, + 'version': 2.0, }, }, }; - final graph = lineUpGraphFromCloudRows([ + final read = lineUpGraphFromCloudRows([ CloudLineupRow(publicId: 'lineup-1', payload: payload), ]); + final graph = read.graph; final landing = graph.landings.single; expect(graph.links.single.name, 'Garage smoke'); @@ -431,6 +435,8 @@ void main() { expect(landing.id, 'landing-1'); expect(landing.ability.lineUpID, 'landing-1'); expect(landing.ability.position, const Offset(30, 40)); + expect(read.originVersions, {'origin-1': 1}); + expect(read.landingVersions, {'landing-1': 2}); }); test('ability info accepts Convex float64 integers and rejects fractions', diff --git a/test/global_strategy_outbox_test.dart b/test/global_strategy_outbox_test.dart index c0558107..461054ba 100644 --- a/test/global_strategy_outbox_test.dart +++ b/test/global_strategy_outbox_test.dart @@ -251,8 +251,8 @@ void main() { pagePublicId: 'page-one', payload: cloudLineupPayload({ 'id': 'k', - 'origin': {'id': 'o', 'agent': {}}, - 'landing': {'id': 'l', 'ability': {}}, + 'origin': {'id': 'o', 'agent': {}, 'version': 1}, + 'landing': {'id': 'l', 'ability': {}, 'version': 1}, }), sortIndex: 0, ); diff --git a/test/remote_page_merge_test.dart b/test/remote_page_merge_test.dart index 2d3b856d..c0c20acf 100644 --- a/test/remote_page_merge_test.dart +++ b/test/remote_page_merge_test.dart @@ -1,4 +1,3 @@ -import 'package:flutter/painting.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:icarus/const/agents.dart'; import 'package:icarus/const/line_provider.dart'; diff --git a/test/strategy_cloud_migration_test.dart b/test/strategy_cloud_migration_test.dart index 511785e9..8fb5e594 100644 --- a/test/strategy_cloud_migration_test.dart +++ b/test/strategy_cloud_migration_test.dart @@ -76,7 +76,10 @@ List _upload( /// What a client hydrates from the uploaded rows, JSON round-tripped as the /// server returns them. -LineUpGraph _hydrate(List adds) { +LineUpGraph _hydrate(List adds) => _hydrated(adds).graph; + +/// [_hydrate], with the version of each spot drawn. +CloudLineups _hydrated(List adds) { return lineUpGraphFromCloudRows([ for (final add in adds) CloudLineupRow( @@ -101,6 +104,9 @@ void main() { final data = cloudPayloadData(adds.single.payload); expect((data['origin'] as Map)['id'], 'origin-1'); expect((data['landing'] as Map)['id'], 'link-1'); + // An upload is the spots' first cloud copy. + expect((data['origin'] as Map)['version'], 1); + expect((data['landing'] as Map)['version'], 1); // Hydrating the upload gives back the page's graph, ids and all, so the // page never authors a patch for a lineup nobody touched. expect( @@ -125,7 +131,8 @@ void main() { expect((data['landing'] as Map)['id'], 'link-1'); // And the rows reproduce exactly when the hydrated graph is sent again. - final resent = cloudLineupRows(_hydrate(adds)); + final hydrated = _hydrated(adds); + final resent = cloudLineupRows(hydrated.graph, drawn: hydrated); expect( _canonical([for (final row in resent) row.payload]), _canonical([for (final add in adds) add.payload]), diff --git a/test/strategy_op_queue_provider_test.dart b/test/strategy_op_queue_provider_test.dart index 0bf60640..4f6d33ac 100644 --- a/test/strategy_op_queue_provider_test.dart +++ b/test/strategy_op_queue_provider_test.dart @@ -1074,7 +1074,6 @@ void main() { final store = MemoryDurableStrategyOutboxStore(); final retired = _retiredLinkAdd(); final retiredKey = EntitySyncKey.forStrategyOp(retired)!; - // Queued by one session, so both are claimed as one batch. await store.put(_savedRecord(_cloudElementOp())); await store.put(_savedRecord(retired)); var repository = _RecordingAckRepository(); @@ -1085,31 +1084,41 @@ void main() { var notifier = container.read(strategyOpQueueProvider.notifier) ..setActiveStrategy('strategy-1', accountId: 'account-a'); + // Shown from the moment the strategy opens, before anything is sent. + var current = container.read(strategyOpQueueProvider); + expect(current.queuedByEntityKey.keys, [ + EntitySyncKey.forStrategyOp(_cloudElementOp()), + ]); + expect(current.attentionByEntityKey.keys, [retiredKey]); + expect(current.lastError, retiredLineupOpMessage); + await notifier.flushNow(); await _settle(); - // The rest of the batch lands; the old change never leaves the device. + // The rest of the strategy's work lands; the old change never leaves + // the device. expect( repository.calls.map((ops) => ops.map((op) => op.opId).toList()), [ ['op-1'], ], ); - var current = container.read(strategyOpQueueProvider); + current = container.read(strategyOpQueueProvider); expect(current.queuedByEntityKey, isEmpty); expect(current.attentionByEntityKey.keys, [retiredKey]); expect(current.needsAttention, isTrue); expect(current.lastError, retiredLineupOpMessage); expect(isSpecificAttentionReason(current.lastError!), isTrue); - final durable = store.load().records.single; - expect(durable.status, DurableOutboxStatus.attention); - expect(durable.pending.op.opId, retired.opId); - expect(durable.pending.op.payload, retired.payload); - expect(durable.lastError, retiredLineupOpMessage); expect( - friendlyCloudSyncError(durable.lastError!), + friendlyCloudSyncError(current.lastError!), contains('older version of Icarus'), ); + // Only the copy in memory changed: the saved record is as the older + // build left it. + final durable = store.load().records.single; + expect(durable.status, DurableOutboxStatus.queued); + expect(durable.pending.op.opId, retired.opId); + expect(durable.pending.op.payload, retired.payload); // After a restart it is still there, still explained, still unsent. container.dispose(); @@ -1128,31 +1137,99 @@ void main() { expect(store.load().records.single.pending.op.opId, retired.opId); }); - test('an old-format lineup change refused by an older build says why', - () async { - final store = MemoryDurableStrategyOutboxStore(); - // An older build left it in attention with its own reason. - await store.put(_savedRecord( - _retiredLinkAdd(), - status: DurableOutboxStatus.attention, - lastError: "This lineup's origin or landing spot is no longer on " - 'the page', - )); - final container = _cloudQueueContainer( - store: store, - repository: _RecordingAckRepository(), - ); - addTearDown(container.dispose); + // B4: whatever state an older build left an old-format record in, it + // waits in attention from the moment the outbox loads, where the + // canvas's reconciliation never reaches it. Left queued, paused or in + // flight it would otherwise be reconciled away (or sent) by the first + // sync of its page. + for (final (status, attempts, lastError) in [ + (DurableOutboxStatus.queued, 0, null), + // Paused after transport failures, with no successor. + (DurableOutboxStatus.paused, 8, 'Connection lost while sending'), + (DurableOutboxStatus.inFlight, 1, null), + // Refused by an older build for a reason of its own. + ( + DurableOutboxStatus.attention, + 1, + "This lineup's origin or landing spot is no longer on the page", + ), + ]) { + test( + 'an old-format lineup add left ${status.name} waits in attention ' + 'from load, through a sync of its page', () async { + final store = MemoryDurableStrategyOutboxStore(); + final retired = _retiredLinkAdd(); + final retiredKey = EntitySyncKey.forStrategyOp(retired)!; + await store.put(DurableOutboxRecord( + accountId: 'account-a', + strategyPublicId: 'strategy-1', + entityKey: retiredKey, + pending: PendingOp( + op: retired, + clientId: 'client-a', + attempts: attempts, + lastAttemptAt: attempts == 0 ? null : DateTime(2026), + ), + status: status, + createdAt: DateTime(2026), + updatedAt: DateTime(2026), + lastError: lastError, + )); + final repository = _RecordingAckRepository(); + final container = _cloudQueueContainer( + store: store, + repository: repository, + ); + addTearDown(container.dispose); + final notifier = container.read(strategyOpQueueProvider.notifier) + ..setActiveStrategy('strategy-1', accountId: 'account-a'); - container - .read(strategyOpQueueProvider.notifier) - .setActiveStrategy('strategy-1', accountId: 'account-a'); + var current = container.read(strategyOpQueueProvider); + expect(current.attentionByEntityKey.keys, [retiredKey]); + expect(current.queuedByEntityKey, isEmpty); + expect(current.pausedByEntityKey, isEmpty); + expect(current.inFlightByEntityKey, isEmpty); + expect(current.lastError, retiredLineupOpMessage); - expect( - container.read(strategyOpQueueProvider).lastError, - retiredLineupOpMessage, - ); - }); + // The canvas syncs the page with other work, clearing what it no + // longer has. + const element = ElementAddOp( + opId: 'element-add', + elementPublicId: 'element-1', + pagePublicId: 'page-1', + payload: {'value': 'new'}, + sortIndex: 0, + ); + await notifier.syncDesiredOpsForPage( + pageId: 'page-1', + desiredOpsByEntityKey: { + EntitySyncKey.forStrategyOp(element)!: element, + }, + clearMissing: true, + ); + await notifier.flushNow(); + await _settle(); + + current = container.read(strategyOpQueueProvider); + expect(current.attentionByEntityKey.keys, [retiredKey]); + expect( + current.attentionByEntityKey[retiredKey]!.pending.op.opId, + retired.opId, + ); + expect(current.lastError, retiredLineupOpMessage); + expect( + repository.calls.expand((ops) => ops).map((op) => op.opId), + ['element-add'], + ); + // The element landed and left the outbox; the old change is still + // saved, exactly as the older build saved it. + final durable = store.load().records.single; + expect(durable.entityKey, retiredKey); + expect(durable.pending.op.opId, retired.opId); + expect(durable.pending.op.payload, retired.payload); + expect(durable.status, status); + }); + } test('an old-format lineup delete is shown, and alone sends nothing', () async { @@ -1183,9 +1260,7 @@ void main() { expect(current.attentionByEntityKey.keys, [retiredKey]); expect(current.lastError, retiredLineupOpMessage); final durable = store.load().records.single; - expect(durable.status, DurableOutboxStatus.attention); expect(durable.pending.op.opId, retired.opId); - expect(durable.lastError, retiredLineupOpMessage); }); test('keep mine leaves an old-format lineup change waiting; discard drops', @@ -1243,9 +1318,8 @@ void main() { final durable = store.load().records.singleWhere( (record) => record.entityKey == retiredKey, ); - expect(durable.status, DurableOutboxStatus.attention); expect(durable.pending.op.opId, retired.opId); - expect(durable.lastError, retiredLineupOpMessage); + expect(durable.pending.op.payload, retired.payload); expect( repository.calls.expand((ops) => ops).map((op) => op.opId), isNot(contains(retired.opId)), @@ -2781,6 +2855,195 @@ void main() { }); }); + group('keep mine after a teammate deleted the row', () { + Future<(ProviderContainer, StrategyOpQueueNotifier)> refused( + _TombstoneRepository repository, + MemoryDurableStrategyOutboxStore store, + List ops, + ) async { + final container = _cloudQueueContainer( + store: store, + repository: repository, + ); + addTearDown(container.dispose); + final notifier = container.read(strategyOpQueueProvider.notifier) + ..setActiveStrategy('strategy-1', accountId: 'account-a'); + for (final op in ops) { + await notifier.enqueue(op, flushImmediately: false); + } + await notifier.flushNow(); + await _settle(); + + final current = container.read(strategyOpQueueProvider); + expect( + current.attentionByEntityKey.keys, + unorderedEquals( + [for (final op in ops) EntitySyncKey.forStrategyOp(op)]), + ); + for (final record in store.load().records) { + expect(record.status, DurableOutboxStatus.attention); + expect(record.lastError, OpRejectionReason.deleted.wireName); + expect(record.latestServerRevision, 2); + } + return (container, notifier); + } + + test('an element edit comes back as an add over the tombstone', () async { + final store = MemoryDurableStrategyOutboxStore(); + final repository = _TombstoneRepository(); + const patch = ElementPatchOp( + opId: 'move-element', + elementPublicId: 'element-1', + pagePublicId: 'page-1', + payload: {'value': 'moved'}, + sortIndex: 4, + expectedElementRevision: 1, + ); + final key = EntitySyncKey.forStrategyOp(patch)!; + final (container, notifier) = await refused(repository, store, [patch]); + + await notifier.retryRejected(flushImmediately: false); + + final restore = + container.read(strategyOpQueueProvider).queuedByEntityKey[key]!; + expect( + restore.pending.op, + isA() + .having((op) => op.elementPublicId, 'elementPublicId', 'element-1') + .having((op) => op.pagePublicId, 'pagePublicId', 'page-1') + .having((op) => op.payload, 'payload', {'value': 'moved'}) + .having((op) => op.sortIndex, 'sortIndex', 4) + .having( + (op) => op.expectedElementRevision, + 'expectedElementRevision', + 2, + ), + ); + expect(restore.pending.op.opId, isNot(patch.opId)); + expect( + store.load().records.single.pending.op.opId, + restore.pending.op.opId, + ); + + await notifier.flushNow(); + await _settle(); + + expect(repository.restored.keys, ['element-1']); + expect(repository.restored['element-1']!.opId, restore.pending.op.opId); + final current = container.read(strategyOpQueueProvider); + expect(current.attentionByEntityKey, isEmpty); + expect(current.queuedByEntityKey, isEmpty); + expect(current.lastError, isNull); + expect(store.load().records, isEmpty); + }); + + test('a lineup edit comes back as an add over the tombstone', () async { + final store = MemoryDurableStrategyOutboxStore(); + final repository = _TombstoneRepository(); + final payload = _lineupAdd(opId: 'unused', name: 'moved').payload; + final patch = LineupPatchOp( + opId: 'move-lineup', + lineupPublicId: 'k', + pagePublicId: 'page-1', + payload: payload, + sortIndex: 3, + expectedLineupRevision: 1, + ); + final key = EntitySyncKey.forStrategyOp(patch)!; + final (container, notifier) = await refused(repository, store, [patch]); + + await notifier.retryRejected(flushImmediately: false); + + final restore = + container.read(strategyOpQueueProvider).queuedByEntityKey[key]!; + expect( + restore.pending.op, + isA() + .having((op) => op.lineupPublicId, 'lineupPublicId', 'k') + .having((op) => op.pagePublicId, 'pagePublicId', 'page-1') + .having((op) => op.payload, 'payload', payload) + .having((op) => op.sortIndex, 'sortIndex', 3) + .having( + (op) => op.expectedLineupRevision, + 'expectedLineupRevision', + 2, + ), + ); + expect(restore.pending.op.opId, isNot(patch.opId)); + + await notifier.flushNow(); + await _settle(); + + expect(repository.restored.keys, ['k']); + expect(repository.restored['k']!.opId, restore.pending.op.opId); + final current = container.read(strategyOpQueueProvider); + expect(current.attentionByEntityKey, isEmpty); + expect(current.queuedByEntityKey, isEmpty); + expect(store.load().records, isEmpty); + }); + + test('a reorder, or an edit without its place, cannot come back', () async { + final store = MemoryDurableStrategyOutboxStore(); + final repository = _TombstoneRepository(); + final ops = [ + const ElementReorderOp( + opId: 'reorder-element', + elementPublicId: 'element-1', + pagePublicId: 'page-1', + sortIndex: 2, + expectedElementRevision: 1, + ), + const LineupReorderOp( + opId: 'reorder-lineup', + lineupPublicId: 'k', + pagePublicId: 'page-1', + sortIndex: 2, + expectedLineupRevision: 1, + ), + // A patch that does not say where the element sits. + const ElementPatchOp( + opId: 'patch-no-sort', + elementPublicId: 'element-2', + pagePublicId: 'page-1', + payload: {'value': 'moved'}, + expectedElementRevision: 1, + ), + ]; + final (container, notifier) = await refused(repository, store, ops); + + await notifier.retryRejected(flushImmediately: false); + // Keep mine says why it could not help, not that a revision is missing. + expect( + container.read(strategyOpQueueProvider).lastError, + teammateDeletedCannotRestoreMessage, + ); + await notifier.flushNow(); + await _settle(); + + final current = container.read(strategyOpQueueProvider); + expect(current.queuedByEntityKey, isEmpty); + expect( + { + for (final intent in current.attentionByEntityKey.values) + intent.pending.op.opId, + }, + {for (final op in ops) op.opId}, + ); + expect(current.lastError, isNotNull); + expect(repository.calls, hasLength(1)); + expect(repository.restored, isEmpty); + final durable = store.load().records; + expect( + {for (final record in durable) record.pending.op.opId}, + {for (final op in ops) op.opId}, + ); + for (final record in durable) { + expect(record.status, DurableOutboxStatus.attention); + expect(record.lastError, OpRejectionReason.deleted.wireName); + } + }); + }); + group('a server that needs a newer Icarus', () { const key = EntitySyncKey.element('page-1', 'element-1'); @@ -3020,8 +3283,8 @@ LineupAddOp _lineupAdd({ payload: cloudLineupPayload({ 'id': id, 'name': name, - 'origin': {'id': 'o', 'agent': {}}, - 'landing': {'id': 'l', 'ability': {}}, + 'origin': {'id': 'o', 'agent': {}, 'version': 1}, + 'landing': {'id': 'l', 'ability': {}, 'version': 1}, }), sortIndex: 0, ); @@ -3210,6 +3473,50 @@ class _FailingLineupRepository extends ConvexStrategyRepository { } } +/// A server on which a teammate deleted elements element-1 and element-2 and +/// lineup k, each tombstone at revision 2. A patch or reorder of a deleted +/// row is refused as deleted; an add expecting the tombstone's revision +/// brings the row back. +class _TombstoneRepository extends ConvexStrategyRepository { + _TombstoneRepository() : super(IcarusConvexApi(_UnusedTransport())); + + final List> calls = []; + final _deleted = {'element-1', 'element-2', 'k'}; + + /// The add that restored each row, by its id. + final Map restored = {}; + + @override + Future> applyBatch({ + required String strategyPublicId, + required String clientId, + required List ops, + String? accountSubject, + }) async { + calls.add(List.from(ops)); + return [for (final op in ops) _apply(op)]; + } + + OpAck _apply(StrategyOp op) { + final id = op.entityPublicId!; + if (!_deleted.contains(id)) return AppliedOpAck(opId: op.opId, revision: 2); + if (op.kind == StrategyOpKind.add && op.expectedRevision == 2) { + _deleted.remove(id); + restored[id] = op; + return AppliedOpAck(opId: op.opId, revision: 3); + } + return RejectedOpAck( + opId: op.opId, + rejectionReason: op.kind == StrategyOpKind.add + ? OpRejectionReason.revisionMismatch + : OpRejectionReason.deleted, + current: op.entityType == StrategyOpEntityType.lineup + ? const LineupCurrentSnapshot(revision: 2, value: {}) + : const ElementCurrentSnapshot(revision: 2, value: {}), + ); + } +} + class _SequencedAckRepository extends ConvexStrategyRepository { _SequencedAckRepository() : super(IcarusConvexApi(_UnusedTransport())); diff --git a/test/strategy_page_session_provider_test.dart b/test/strategy_page_session_provider_test.dart index 11cd1434..ff6fbb98 100644 --- a/test/strategy_page_session_provider_test.dart +++ b/test/strategy_page_session_provider_test.dart @@ -7,6 +7,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:hive_ce/hive.dart'; import 'package:icarus/collab/cloud_lineup_rows.dart'; +import 'package:icarus/collab/canonical_json.dart'; import 'package:icarus/collab/cloud_media_models.dart'; import 'package:icarus/collab/collab_models.dart'; import 'package:icarus/collab/convex_strategy_repository.dart'; @@ -244,6 +245,41 @@ class _FakeStrategyOpQueueNotifier extends StrategyOpQueueNotifier { ); } + /// Takes the server's [acks] for the ops [sent], as the real queue does, + /// in one update: an op the server took leaves the queue, a refused one + /// waits in attention, and one it never answered (its send failed) stays + /// queued. + void answer(Map sent, List acks) { + final ackByOpId = {for (final ack in acks) ack.opId: ack}; + final queued = Map.from( + state.queuedByEntityKey, + ); + final attention = Map.from( + state.attentionByEntityKey, + ); + final answered = []; + for (final MapEntry(:key, value: op) in sent.entries) { + final ack = ackByOpId[op.opId]; + if (ack == null) continue; + if (queued[key]?.pending.op.opId == op.opId) queued.remove(key); + if (!ack.isAck) { + attention[key] = QueuedEntityIntent( + entityKey: key, + pending: PendingOp(op: op, clientId: 'test-client'), + ); + } + answered.add(AckedEntityIntent(entityKey: key, op: op, ack: ack)); + } + state = state.copyWith( + queuedByEntityKey: queued, + attentionByEntityKey: attention, + lastError: attention.isEmpty ? null : 'Some saved work needs attention.', + clearError: attention.isEmpty, + lastAcks: [for (final acked in answered) acked.ack], + lastAckBatch: answered, + ); + } + @override Future> discardRejected( Set entityKeys, @@ -458,6 +494,44 @@ Future _settle() async { await Future.delayed(Duration.zero); } +/// Waits, in real time, until [condition] holds. +Future _until(bool Function() condition) async { + final deadline = DateTime.now().add(const Duration(seconds: 5)); + while (!condition()) { + if (DateTime.now().isAfter(deadline)) { + throw TimeoutException('The condition never held.'); + } + await Future.delayed(const Duration(milliseconds: 10)); + } +} + +/// Plays the server for the real queue: [server] answers every batch, and +/// [afterBatch] runs once it has, before the answers reach the queue (as +/// the live read showing the batch can arrive before its answers). +class _ServerRepository implements ConvexStrategyRepository { + _ServerRepository(this.server, {required this.afterBatch}); + + final _FakeServer server; + final void Function() afterBatch; + final List> batches = []; + + @override + Future> applyBatch({ + required String strategyPublicId, + required String clientId, + required List ops, + String? accountSubject, + }) async { + batches.add(ops); + final acks = server.applyBatch(ops); + afterBatch(); + return acks; + } + + @override + dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); +} + RemotePage _page(String id, int index, {int revision = 1, String? name, bool isAttack = true}) { final now = DateTime.utc(2026); @@ -557,19 +631,21 @@ Map _abilityJson( 'armLengthsMeters': [10, 10, 10, 10], }; -/// Origin [id] as a lineup row carries it. +/// Origin [id] as a lineup row carries it, at its spot's [version]. Map _originJson( String id, [ Offset position = const Offset(10, 20), + int version = 1, ]) => - {'id': id, 'agent': _agentJson(id, position)}; + {'id': id, 'agent': _agentJson(id, position), 'version': version}; -/// Landing [id] as a lineup row carries it. +/// Landing [id] as a lineup row carries it, at its spot's [version]. Map _landingJson( String id, [ Offset position = const Offset(30, 40), + int version = 1, ]) => - {'id': id, 'ability': _abilityJson(id, position)}; + {'id': id, 'ability': _abilityJson(id, position), 'version': version}; /// One lineup as the server stores it: one row, the link [id] with its own /// copies of its [origin] and [landing]. @@ -647,6 +723,290 @@ RemoteLineup _editedLineup( ); } +/// Plays the server's element and lineup rows of one page as convex/ops.ts +/// keeps them (applyElementOp, applyLineupOp). +/// +/// Every row has its own revision, which each change it takes moves on by +/// one. A delete leaves a tombstone: still listed, deleted, one revision on. +/// A write expecting another revision than the row's is refused with the +/// row as it is. A patch or reorder of a tombstone is refused as `deleted`; +/// an add expecting the tombstone's revision brings the row back. A write +/// that would change nothing is a noop, whatever revision it expected. +class _FakeServer { + _FakeServer( + this.pageId, { + Iterable lineups = const [], + Iterable elements = const [], + }) { + for (final row in lineups) { + _lineups[row.publicId] = _Row.lineup(row); + } + for (final row in elements) { + _elements[row.publicId] = _Row.element(row); + } + } + + final String pageId; + final Map _lineups = {}; + final Map _elements = {}; + + /// Every lineup op this server answered, in order. + final List received = []; + + /// Every lineup row, tombstones included, in stored order. + List get rows => [ + for (final MapEntry(:key, :value) in _sorted(_lineups)) + RemoteLineup( + publicId: key, + strategyPublicId: 'cloud-strategy', + pagePublicId: pageId, + payload: value.payload, + sortIndex: value.sortIndex, + revision: value.revision, + deleted: value.deleted, + ), + ]; + + /// The lineup rows lineups are drawn from. + List get liveRows => [ + for (final row in rows) + if (!row.deleted) row, + ]; + + RemoteLineup row(String id) => rows.singleWhere((row) => row.publicId == id); + + /// Every element row, tombstones included, in stored order. + List get elements => [ + for (final MapEntry(:key, :value) in _sorted(_elements)) + RemoteElement( + publicId: key, + strategyPublicId: 'cloud-strategy', + pagePublicId: pageId, + elementType: value.elementType!, + payload: value.payload, + sortIndex: value.sortIndex, + revision: value.revision, + deleted: value.deleted, + ), + ]; + + RemoteElement element(String id) => + elements.singleWhere((element) => element.publicId == id); + + /// The server's answer to each of [ops], applied in order. An op on + /// anything but an element or a lineup is applied as sent. + List applyBatch(Iterable ops) => + [for (final op in ops) apply(op)]; + + OpAck apply(StrategyOp op) { + final (rows, change) = switch (op) { + LineupAddOp(:final payload, :final sortIndex) => ( + _lineups, + (kind: StrategyOpKind.add, payload: payload, sortIndex: sortIndex), + ), + LineupPatchOp(:final payload, :final sortIndex) => ( + _lineups, + (kind: StrategyOpKind.patch, payload: payload, sortIndex: sortIndex), + ), + LineupReorderOp(:final sortIndex) => ( + _lineups, + (kind: StrategyOpKind.reorder, payload: null, sortIndex: sortIndex), + ), + LineupDeleteOp() => ( + _lineups, + (kind: StrategyOpKind.delete, payload: null, sortIndex: null), + ), + ElementAddOp(:final payload, :final sortIndex) => ( + _elements, + (kind: StrategyOpKind.add, payload: payload, sortIndex: sortIndex), + ), + ElementPatchOp(:final payload, :final sortIndex) => ( + _elements, + (kind: StrategyOpKind.patch, payload: payload, sortIndex: sortIndex), + ), + ElementReorderOp(:final sortIndex) => ( + _elements, + (kind: StrategyOpKind.reorder, payload: null, sortIndex: sortIndex), + ), + ElementDeleteOp() => ( + _elements, + (kind: StrategyOpKind.delete, payload: null, sortIndex: null), + ), + _ => (null, null), + }; + if (rows == null || change == null) { + return AppliedOpAck( + opId: op.opId, + revision: (op.expectedRevision ?? 0) + 1, + ); + } + if (identical(rows, _lineups)) received.add(op); + final id = op.entityPublicId!; + final existing = rows[id]; + OpAck refused(OpRejectionReason reason) => RejectedOpAck( + opId: op.opId, + rejectionReason: reason, + current: existing == null + ? null + : identical(rows, _lineups) + ? LineupCurrentSnapshot( + revision: existing.revision, + value: existing.payload, + ) + : ElementCurrentSnapshot( + revision: existing.revision, + value: existing.payload, + ), + ); + OpAck? stale() => op.expectedRevision == null + ? refused(OpRejectionReason.missingExpectedRevision) + : op.expectedRevision != existing!.revision + ? refused(OpRejectionReason.revisionMismatch) + : null; + OpAck store(CloudPayload payload, int sortIndex, {bool deleted = false}) { + final revision = (existing?.revision ?? 0) + 1; + rows[id] = _Row( + payload: payload, + sortIndex: sortIndex, + revision: revision, + deleted: deleted, + elementType: identical(rows, _lineups) + ? null + : existing?.elementType ?? payload['kind'] as String, + ); + return AppliedOpAck(opId: op.opId, revision: revision); + } + + OpAck noop() => + NoopOpAck(opId: op.opId, currentRevision: existing?.revision); + + switch (change.kind) { + case StrategyOpKind.add: + final payload = change.payload!; + final sortIndex = change.sortIndex!; + if (existing == null) return store(payload, sortIndex); + if (existing.deleted) return stale() ?? store(payload, sortIndex); + return _sameJson(existing.payload, payload) + ? noop() + : refused(OpRejectionReason.alreadyExists); + case StrategyOpKind.delete: + if (existing == null || existing.deleted) return noop(); + return stale() ?? + store(existing.payload, existing.sortIndex, deleted: true); + case StrategyOpKind.patch || StrategyOpKind.reorder: + if (existing == null) return refused(OpRejectionReason.notFound); + if (existing.deleted) return refused(OpRejectionReason.deleted); + final payload = change.payload ?? existing.payload; + final sortIndex = change.sortIndex ?? existing.sortIndex; + if (_sameJson(payload, existing.payload) && + sortIndex == existing.sortIndex) { + return noop(); + } + return stale() ?? store(payload, sortIndex); + } + } + + /// A teammate's change to lineup row [id] lands: [edit] changes its data, + /// one revision on. + void teammateEdit(String id, void Function(Map data) edit) { + final row = _lineups[id]!; + final payload = jsonDecode(jsonEncode(row.payload)) as CloudPayload; + edit(payload['data'] as Map); + if (_sameJson(payload, row.payload)) return; + _lineups[id] = row.next(payload: payload); + } + + /// A teammate's delete of lineup row [id] lands. + void teammateDelete(String id) => + _lineups[id] = _lineups[id]!.next(deleted: true); + + /// A teammate's delete of element row [id] lands. + void teammateDeleteElement(String id) => + _elements[id] = _elements[id]!.next(deleted: true); + + /// A teammate's new lineup [row] lands. + void teammateAdd(RemoteLineup row) { + assert(!_lineups.containsKey(row.publicId)); + _lineups[row.publicId] = _Row( + payload: row.payload, + sortIndex: row.sortIndex, + revision: 1, + deleted: false, + ); + } + + static Iterable> _sorted(Map rows) => + rows.entries.toList() + ..sort((a, b) => a.value.sortIndex != b.value.sortIndex + ? a.value.sortIndex.compareTo(b.value.sortIndex) + : a.key.compareTo(b.key)); + + static bool _sameJson(Object? left, Object? right) => + canonicalCloudJsonEncode(jsonDecode(jsonEncode(left))) == + canonicalCloudJsonEncode(jsonDecode(jsonEncode(right))); +} + +/// One row as [_FakeServer] stores it. +class _Row { + _Row({ + required CloudPayload payload, + required this.sortIndex, + required this.revision, + required this.deleted, + this.elementType, + }) : payload = jsonDecode(jsonEncode(payload)) as CloudPayload; + + _Row.lineup(RemoteLineup row) + : this( + payload: row.payload, + sortIndex: row.sortIndex, + revision: row.revision, + deleted: row.deleted, + ); + + _Row.element(RemoteElement row) + : this( + payload: row.payload, + sortIndex: row.sortIndex, + revision: row.revision, + deleted: row.deleted, + elementType: row.elementType, + ); + + final CloudPayload payload; + final int sortIndex; + final int revision; + final bool deleted; + final String? elementType; + + /// This row one revision on, as a teammate's write leaves it. + _Row next({CloudPayload? payload, bool? deleted}) => _Row( + payload: payload ?? this.payload, + sortIndex: sortIndex, + revision: revision + 1, + deleted: deleted ?? this.deleted, + elementType: elementType, + ); +} + +/// Moves the version of [end] (a lineup row's origin or landing) on by one, +/// as a client changing that spot does. +void _bumpVersion(Map end) => end['version'] = (end['version'] as int) + 1; + +/// The origin and landing ids and versions row [row] carries. +({String origin, int originVersion, String landing, int landingVersion}) + _endsOf(RemoteLineup row) { + final data = cloudPayloadData(row.payload); + final origin = data['origin'] as Map; + final landing = data['landing'] as Map; + return ( + origin: origin['id'] as String, + originVersion: origin['version'] as int, + landing: landing['id'] as String, + landingVersion: landing['version'] as int, + ); +} + RemoteEditorSnapshot _editorSnapshot({ required List pages, required RemotePageSnapshot activePage, @@ -902,10 +1262,10 @@ void main() { ); }); - QueuedEntityIntent refusedOnPage2() => QueuedEntityIntent( - entityKey: const EntitySyncKey.element('page-2', 'text-2'), + QueuedEntityIntent refusedOnPage2() => const QueuedEntityIntent( + entityKey: EntitySyncKey.element('page-2', 'text-2'), pending: PendingOp( - op: const ElementDeleteOp( + op: ElementDeleteOp( opId: 'refused-op', pagePublicId: 'page-2', elementPublicId: 'text-2', @@ -997,7 +1357,7 @@ void main() { ); const key = EntitySyncKey.element('page-2', 'text-2'); queue.state = queue.state.copyWith(attentionByEntityKey: { - key: QueuedEntityIntent( + key: const QueuedEntityIntent( entityKey: key, pending: PendingOp(op: refused, clientId: 'test-client'), ), @@ -1684,10 +2044,10 @@ void main() { final (:container, :queue, :two, one: _, remote: _, strokeId: _) = await strokeOnDeletedPage(); // Another page's edit, waiting on the server (paused, say). - final otherKey = EntitySyncKey.element('page-3', 'other'); + const otherKey = EntitySyncKey.element('page-3', 'other'); await queue.syncDesiredGenericOp( entityKey: otherKey, - desiredOp: ElementDeleteOp( + desiredOp: const ElementDeleteOp( opId: 'other-op', pagePublicId: 'page-3', elementPublicId: 'other', @@ -4398,70 +4758,82 @@ void main() { await _settle(); }); - test('copies of a shared spot that disagree draw one copy and send nothing', - () async { + test( + 'copies of a shared spot that disagree draw the highest version, a tie ' + 'the greatest lineup id', () async { final page = _page('page-1', 0); // Teammates moved the shared origin and the shared landing at the same - // time, so the rows carrying each disagree. + // time, so the rows carrying each disagree. Row revisions rank nothing: + // the row with the newest origin has the lowest. final container = await openCloudPage(page, [ _lineupRow(page.publicId, 'link-1', - origin: _originJson('origin', const Offset(100, 100)), + origin: _originJson('origin', const Offset(100, 100), 5), landing: _landingJson('landing-1'), - revision: 5), + revision: 1), _lineupRow(page.publicId, 'link-2', - origin: _originJson('origin', const Offset(200, 200)), - landing: _landingJson('landing', const Offset(220, 220)), - revision: 3, + origin: _originJson('origin', const Offset(200, 200), 3), + landing: _landingJson('landing', const Offset(220, 220), 3), + revision: 9, sortIndex: 1), _lineupRow(page.publicId, 'link-3', origin: _originJson('origin-3'), - landing: _landingJson('landing', const Offset(330, 330)), - revision: 3, + landing: _landingJson('landing', const Offset(330, 330), 3), + revision: 2, sortIndex: 2), ]); - // The highest revision's copy is drawn; a tie goes to the greatest id. final lineUps = container.read(lineUpProvider); expect( lineUps.links.map((link) => link.id), ['link-1', 'link-2', 'link-3']); expect(lineUps.origins.map((origin) => origin.id), ['origin', 'origin-3']); + // Version 5 outranks version 3, whatever the rows' revisions. expect( lineUps.originById('origin')!.agent.position, const Offset(100, 100)); expect(lineUps.landings.map((landing) => landing.id), ['landing-1', 'landing']); + // Both copies are at version 3: link-3's id is the greater. expect(lineUps.landingById('landing')!.ability.position, const Offset(330, 330)); - // Choosing a copy is not an edit: live sync writes no row for it. - expect(lineupOpsAfterTextEdit(container, page), isEmpty); + // link-2 holds copies of both spots other than the drawn ones, so it is + // written with them, at their versions, against its own revision. The + // rows already holding the drawn copies are left be. await _settle(); - expect( - container.read(strategyOpQueueProvider).pending.where( - (pending) => pending.op.entityType == StrategyOpEntityType.lineup), - isEmpty, - ); + Map queuedLineupOps() => { + for (final entry in container + .read(strategyOpQueueProvider) + .queuedByEntityKey + .entries) + if (entry.key.kind == EntitySyncKeyKind.lineup) + entry.key: entry.value.pending.op, + }; + final link2 = EntitySyncKey.lineup(page.publicId, 'link-2'); + expect(queuedLineupOps().keys, [link2]); + final heal = queuedLineupOps()[link2]! as LineupPatchOp; + expect(heal.expectedLineupRevision, 9); + void expectDrawnCopies(CloudPayload payload) { + final data = cloudPayloadData(payload); + final origin = data['origin'] as Map; + final landing = data['landing'] as Map; + expect(origin['version'], 5); + expect((origin['agent'] as Map)['position'], {'dx': 100.0, 'dy': 100.0}); + expect(landing['version'], 3); + expect( + (landing['ability'] as Map)['position'], {'dx': 330.0, 'dy': 330.0}); + } + + expectDrawnCopies(heal.payload!); - // A real edit to a lineup whose copy lost sends the drawn copy. + // A real edit to that lineup carries the drawn copies with it. container .read(lineUpProvider.notifier) .updateLink(lineUps.linkById('link-2')!.copyWith(notes: 'jump throw')); - final ops = container - .read(activePageLiveSyncProvider.notifier) - .syncLocalPage( - strategyPublicId: 'cloud-strategy', pageId: page.publicId)!; - final lineupOps = { - for (final entry in ops.entries) - if (entry.key.kind == EntitySyncKeyKind.lineup) entry.key: entry.value, - }; - expect(lineupOps.keys, [EntitySyncKey.lineup(page.publicId, 'link-2')]); - final patch = lineupOps.values.single as LineupPatchOp; - expect(patch.expectedLineupRevision, 3); - final data = cloudPayloadData(patch.payload); - expect(data['notes'], 'jump throw'); - expect(((data['origin'] as Map)['agent'] as Map)['position'], - {'dx': 100.0, 'dy': 100.0}); - expect(((data['landing'] as Map)['ability'] as Map)['position'], - {'dx': 330.0, 'dy': 330.0}); + await _settle(); + expect(queuedLineupOps().keys, [link2]); + final patch = queuedLineupOps()[link2]! as LineupPatchOp; + expect(patch.expectedLineupRevision, 9); + expect(cloudPayloadData(patch.payload)['notes'], 'jump throw'); + expectDrawnCopies(patch.payload!); await _settle(); }); @@ -4633,18 +5005,22 @@ void main() { group('lineup history through cloud sync', () { const placedAt = Offset(100, 100); late _FakeStrategyOpQueueNotifier queue; + late _FakeServer server; /// Page settings that round-trip exactly, so live sync authors nothing /// for them; the agent size marks which server revision is on screen. CloudPayload settingsFor(int contentRevision) => StrategySettings(agentSize: 30 + contentRevision.toDouble()).toJson(); + /// Opens page-1 with the lineup [rows] the server holds. Future<(ProviderContainer, _FakeRemoteEditorNotifier, RemotePage)> - openEmpty() async { + openEmpty([List rows = const []]) async { final page = _page('page-1', 0); + server = _FakeServer(page.publicId, lineups: rows); final remote = _FakeRemoteEditorNotifier(_editorSnapshot( pages: [page], - activePage: _pageSnapshot(page, settings: settingsFor(1)), + activePage: + _pageSnapshot(page, settings: settingsFor(1), lineups: server.rows), )); queue = _FakeStrategyOpQueueNotifier(); final container = await _cloudContainer(remote: remote, queue: queue); @@ -4685,74 +5061,6 @@ void main() { const {}; } - /// The lineup rows the server stores once [ops] apply to [previous]: - /// exactly the rows the client sent, JSON round-tripped, in stored - /// order. [teammate] edits each stored row's lineup data in the same - /// round, as a teammate's change landing alongside ours would; a row it - /// changes takes this round's [revision]. - List apply( - Iterable ops, - RemotePage page, { - required int revision, - required List previous, - void Function(Map data)? teammate, - }) { - final byId = {for (final lineup in previous) lineup.publicId: lineup}; - RemoteLineup stored(String id, CloudPayload payload, int sortIndex) => - RemoteLineup( - publicId: id, - strategyPublicId: 'cloud-strategy', - pagePublicId: page.publicId, - payload: jsonDecode(jsonEncode(payload)) as Map, - sortIndex: sortIndex, - revision: revision, - deleted: false, - ); - for (final op in ops) { - final (id, payload, sortIndex) = switch (op) { - LineupAddOp( - :final lineupPublicId, - :final payload, - :final sortIndex - ) => - (lineupPublicId, payload, sortIndex), - LineupPatchOp( - :final lineupPublicId, - :final payload, - :final sortIndex - ) => - ( - lineupPublicId, - payload ?? byId[lineupPublicId]!.payload, - sortIndex ?? byId[lineupPublicId]!.sortIndex, - ), - LineupDeleteOp(:final lineupPublicId) => (lineupPublicId, null, 0), - _ => (null, null, 0), - }; - if (id == null) continue; - if (payload == null) { - byId.remove(id); - continue; - } - byId[id] = stored(id, payload, sortIndex); - } - if (teammate != null) { - for (final lineup in [...byId.values]) { - final payload = - jsonDecode(jsonEncode(lineup.payload)) as Map; - teammate(payload['data'] as Map); - if (jsonEncode(payload) != jsonEncode(lineup.payload)) { - byId[lineup.publicId] = - stored(lineup.publicId, payload, lineup.sortIndex); - } - } - } - return byId.values.toList() - ..sort((a, b) => a.sortIndex != b.sortIndex - ? a.sortIndex.compareTo(b.sortIndex) - : a.publicId.compareTo(b.publicId)); - } - RemoteEditorSnapshot serverSnapshot( RemotePage page, List lineups, @@ -4797,23 +5105,6 @@ void main() { entry.key: entry.value, }; - /// One queue update drains [ops] and publishes their acks. - void publishAcks(Map ops, int revision) { - final acks = [ - for (final entry in ops.entries) - AckedEntityIntent( - entityKey: entry.key, - op: entry.value, - ack: AppliedOpAck(opId: entry.value.opId, revision: revision), - ), - ]; - queue.state = queue.state.copyWith( - queuedByEntityKey: const {}, - lastAcks: [for (final intent in acks) intent.ack], - lastAckBatch: acks, - ); - } - /// The stored row of lineup [id] in [lineups]. RemoteLineup rowOf(List lineups, String id) => lineups.singleWhere((row) => row.publicId == id); @@ -4829,29 +5120,46 @@ void main() { ); } - /// The edits' scheduled sync queues ops; one queue update then drains - /// them and publishes their acks (which also marks the page saved), and - /// the session reconciles the acks, refreshes from the server and - /// rehydrates, as in production. Returns what the server holds. + /// The server answers [sent]; the session's refresh after the answers + /// reads the server as it then is, at [contentRevision]. [teammate] + /// edits every live row's lineup data in the same round, as a teammate's + /// change landing alongside ours would. One queue update then takes the + /// answers (which also marks the page saved once nothing is left). + void send( + _FakeRemoteEditorNotifier remote, + RemotePage page, + Map sent, { + required int contentRevision, + void Function(Map data)? teammate, + }) { + final acks = server.applyBatch(sent.values); + if (teammate != null) { + for (final row in server.liveRows) { + server.teammateEdit(row.publicId, teammate); + } + } + expectEveryRowDrawn(server.liveRows); + remote.initialSnapshot = + serverSnapshot(page, server.rows, contentRevision); + queue.answer(sent, acks); + } + + /// The edits' scheduled sync queues ops; the server answers all of them + /// ([send]), and the session reconciles the answers, refreshes from the + /// server and rehydrates, as in production. Returns the live rows the + /// server holds. Future> land( ProviderContainer container, _FakeRemoteEditorNotifier remote, RemotePage page, { - required List previous, - required int revision, required int contentRevision, void Function(Map data)? teammate, }) async { await _settle(); - final pending = queuedOps(); - final lineups = apply(pending.values, page, - revision: revision, previous: previous, teammate: teammate); - expectEveryRowDrawn(lineups); - // The session fetches this on the refresh that follows the acks. - remote.initialSnapshot = serverSnapshot(page, lineups, contentRevision); - publishAcks(pending, revision); + send(remote, page, queuedOps(), + contentRevision: contentRevision, teammate: teammate); await settleSync(container, contentRevision); - return lineups; + return server.liveRows; } /// Every lineup write is one whole lineup row, keyed by its link. @@ -4875,8 +5183,7 @@ void main() { container .read(lineUpProvider.notifier) .updateLink(link.copyWith(notes: 'jump throw')); - await land(container, remote, page, - previous: const [], revision: 1, contentRevision: 2); + await land(container, remote, page, contentRevision: 2); container.read(actionProvider.notifier).undoAction(); @@ -4898,8 +5205,7 @@ void main() { container .read(lineUpProvider.notifier) .updateLandingPosition(link.landingId, const Offset(320, 340)); - await land(container, remote, page, - previous: const [], revision: 1, contentRevision: 2); + await land(container, remote, page, contentRevision: 2); expect(container.read(lineUpProvider).landings.single.id, link.landingId); @@ -4921,23 +5227,19 @@ void main() { test('undoing a move keeps a teammate weapon on the same origin', () async { final (container, remote, page) = await openEmpty(); final link = place(container); - var lineups = await land(container, remote, page, - previous: const [], revision: 1, contentRevision: 2); + await land(container, remote, page, contentRevision: 2); container .read(lineUpProvider.notifier) .updateOriginAgentPosition(link.originId, const Offset(500, 500)); - lineups = await land(container, remote, page, - previous: lineups, revision: 2, contentRevision: 3); + await land(container, remote, page, contentRevision: 3); // Then a teammate arms the same origin. - remote.setSnapshot(serverSnapshot( - page, - [ - _editedLineup(rowOf(lineups, link.id), (data) { - ((data['origin'] as Map)['agent'] as Map)['weapon'] = 'vandal'; - }), - ], - 4)); + server.teammateEdit(link.id, (data) { + final origin = data['origin'] as Map; + (origin['agent'] as Map)['weapon'] = 'vandal'; + _bumpVersion(origin); + }); + remote.setSnapshot(serverSnapshot(page, server.rows, 4)); await settleSync(container, 4); expect( container.read(lineUpProvider).originById(link.originId)!.agent.weapon, @@ -4960,16 +5262,16 @@ void main() { () async { final (container, remote, page) = await openEmpty(); final link = place(container); - final lineups = await land(container, remote, page, - previous: const [], revision: 1, contentRevision: 2); + await land(container, remote, page, contentRevision: 2); final lineUps = container.read(lineUpProvider.notifier); const moved = Offset(500, 500); lineUps.updateOriginAgentPosition(link.originId, moved); lineUps.deleteOrigin(link.originId); - final after = await land(container, remote, page, - previous: lineups, revision: 2, contentRevision: 3); + final after = await land(container, remote, page, contentRevision: 3); expect(after, isEmpty); + // The server keeps the deleted lineup as a tombstone. + expect(server.row(link.id).deleted, isTrue); final history = container.read(actionProvider.notifier); Offset? originPosition() => container @@ -5044,8 +5346,7 @@ void main() { final before = container.read(lineUpProvider); final originIds = before.origins.map((origin) => origin.id).toList(); - final lineups = await land(container, remote, page, - previous: const [], revision: 1, contentRevision: 2); + final lineups = await land(container, remote, page, contentRevision: 2); // One row per lineup, each carrying its own copy of the landing. expect(spotsOf(lineups), { @@ -5074,8 +5375,7 @@ void main() { .map((landing) => landing.id) .toList(); - final lineups = await land(container, remote, page, - previous: const [], revision: 1, contentRevision: 2); + final lineups = await land(container, remote, page, contentRevision: 2); expect(spotsOf(lineups), { a.id: (a.originId, a.landingId), @@ -5099,13 +5399,11 @@ void main() { final (container, remote, page) = await openEmpty(); final (a, b) = placeFanIn(container); final other = place(container); - var lineups = await land(container, remote, page, - previous: const [], revision: 1, contentRevision: 2); + var lineups = await land(container, remote, page, contentRevision: 2); // A's row moves on to revision 2; B's and the other's stay at 1. container.read(lineUpProvider.notifier).updateLink( container.read(lineUpProvider).linkById(a.id)!.copyWith(notes: 'x')); - lineups = await land(container, remote, page, - previous: lineups, revision: 2, contentRevision: 3); + lineups = await land(container, remote, page, contentRevision: 3); expect( {for (final row in lineups) row.publicId: row.revision}, {a.id: 2, b.id: 1, other.id: 1}, @@ -5132,10 +5430,19 @@ void main() { final landing = cloudPayloadData(op.payload)['landing'] as Map; expect((landing['ability'] as Map)['position'], {'dx': moved.dx, 'dy': moved.dy}); + // The moved landing outranks every copy the rows hold. + expect(landing['version'], 2); } - lineups = await land(container, remote, page, - previous: lineups, revision: 3, contentRevision: 4); + lineups = await land(container, remote, page, contentRevision: 4); + expect( + {for (final row in lineups) row.publicId: row.revision}, + {a.id: 3, b.id: 2, other.id: 1}, + ); + expect( + {for (final row in lineups) row.publicId: _endsOf(row).landingVersion}, + {a.id: 2, b.id: 2, other.id: 1}, + ); final hydrated = container.read(lineUpProvider); expect(hydrated.landingById(a.landingId)!.ability.position, moved); expect(hydrated.landings, hasLength(2)); @@ -5146,11 +5453,9 @@ void main() { test('undoing a deletion rejoins the surviving shared landing', () async { final (container, remote, page) = await openEmpty(); final (a, b) = placeFanIn(container); - var lineups = await land(container, remote, page, - previous: const [], revision: 1, contentRevision: 2); + var lineups = await land(container, remote, page, contentRevision: 2); container.read(lineUpProvider.notifier).deleteOrigin(a.originId); - lineups = await land(container, remote, page, - previous: lineups, revision: 2, contentRevision: 3); + lineups = await land(container, remote, page, contentRevision: 3); expect(spotsOf(lineups), {b.id: (b.originId, a.landingId)}); container.read(actionProvider.notifier).undoAction(); @@ -5164,9 +5469,18 @@ void main() { final ops = desiredOps(container, page); expectLineupRows(ops); expect(ops.keys, [keyOf(page, a.id)]); - expect(ops.values.single, isA()); - lineups = await land(container, remote, page, - previous: lineups, revision: 3, contentRevision: 4); + // An add over the tombstone the delete left, expecting its revision. + expect(server.row(a.id).deleted, isTrue); + expect( + (ops.values.single as LineupAddOp).expectedLineupRevision, + server.row(a.id).revision, + ); + lineups = await land(container, remote, page, contentRevision: 4); + expect(spotsOf(lineups), { + b.id: (b.originId, a.landingId), + a.id: (a.originId, a.landingId), + }); + expect(server.row(a.id).revision, 3); expect(linkShape(container.read(lineUpProvider)), hasLength(2)); await _settle(); }); @@ -5176,8 +5490,7 @@ void main() { 'shared one', () async { final (container, remote, page) = await openEmpty(); final (a, b) = placeFanIn(container); - var lineups = await land(container, remote, page, - previous: const [], revision: 1, contentRevision: 2); + var lineups = await land(container, remote, page, contentRevision: 2); container.read(lineUpProvider.notifier).deleteLink(a.id); @@ -5189,8 +5502,7 @@ void main() { final ops = desiredOps(container, page); expect(ops.keys, [keyOf(page, a.id)]); expect(ops.values.single, isA()); - lineups = await land(container, remote, page, - previous: lineups, revision: 2, contentRevision: 3); + lineups = await land(container, remote, page, contentRevision: 3); expect(spotsOf(lineups), {b.id: (b.originId, a.landingId)}); graph = container.read(lineUpProvider).graph; expect(graph.links.map((link) => link.id), [b.id]); @@ -5201,8 +5513,7 @@ void main() { graph = container.read(lineUpProvider).graph; expect(graph.origins, isEmpty); expect(graph.landings, isEmpty); - lineups = await land(container, remote, page, - previous: lineups, revision: 3, contentRevision: 4); + lineups = await land(container, remote, page, contentRevision: 4); expect(lineups, isEmpty); expect(container.read(lineUpProvider).graph.landings, isEmpty); await _settle(); @@ -5212,8 +5523,7 @@ void main() { () async { final (container, remote, page) = await openEmpty(); final (a, b) = placeFanIn(container); - var lineups = await land(container, remote, page, - previous: const [], revision: 1, contentRevision: 2); + await land(container, remote, page, contentRevision: 2); container .read(lineUpProvider.notifier) .updateLink(container.read(lineUpProvider).linkById(a.id)!.copyWith( @@ -5232,12 +5542,10 @@ void main() { 1, ); - lineups = await land( + await land( container, remote, page, - previous: lineups, - revision: 2, contentRevision: 3, teammate: (data) { if (data['id'] == b.id) data['notes'] = 'teammate notes'; @@ -5256,8 +5564,7 @@ void main() { test('a stale edit to a lineup a teammate changed conflicts', () async { final (container, remote, page) = await openEmpty(); final link = place(container); - final lineups = await land(container, remote, page, - previous: const [], revision: 1, contentRevision: 2); + await land(container, remote, page, contentRevision: 2); container .read(lineUpProvider.notifier) .updateLink(link.copyWith(notes: 'mine')); @@ -5267,14 +5574,8 @@ void main() { expect(mine.expectedLineupRevision, 1); // A teammate's notes edit to the same lineup lands first. - remote.setSnapshot(serverSnapshot( - page, - [ - _editedLineup( - rowOf(lineups, link.id), (data) => data['notes'] = 'theirs'), - ], - 3, - )); + server.teammateEdit(link.id, (data) => data['notes'] = 'theirs'); + remote.setSnapshot(serverSnapshot(page, server.rows, 3)); await _settle(); // Still checked against the version the user saw: never re-based onto // theirs, which would overwrite it in silence. @@ -5284,8 +5585,16 @@ void main() { ); // The server refuses it; the user sees their work and a conflict. - queue.reject(queuedOps()[key]!); + send(remote, page, queuedOps(), contentRevision: 3); await _settle(); + expect( + queue.state.lastAckBatch + .singleWhere((acked) => acked.entityKey == key) + .ack, + isA().having((ack) => ack.rejectionReason, 'reason', + OpRejectionReason.revisionMismatch), + ); + expect(cloudPayloadData(server.row(link.id).payload)['notes'], 'theirs'); expect(container.read(lineUpProvider).links.single.notes, 'mine'); expect(queue.state.needsAttention, isTrue); expect(queue.state.attentionByEntityKey.keys, [key]); @@ -5310,12 +5619,11 @@ void main() { 'theirs whole', () async { final (container, remote, page) = await openEmpty(); final first = place(container); - final landed = await land(container, remote, page, - previous: const [], revision: 1, contentRevision: 2); + final landed = await land(container, remote, page, contentRevision: 2); // A teammate's lineup from the same origin, carrying their own copy // of it, lands; this canvas has not drawn it yet. - final theirs = RemoteLineup( + server.teammateAdd(RemoteLineup( publicId: 'their-link', strategyPublicId: 'cloud-strategy', pagePublicId: page.publicId, @@ -5328,7 +5636,7 @@ void main() { sortIndex: 10, revision: 1, deleted: false, - ); + )); // The user deletes the origin, which takes their only lineup on it. container.read(lineUpProvider.notifier).deleteOrigin(first.originId); @@ -5338,9 +5646,10 @@ void main() { expect(lineupOps(sent).values.single, isA()); // The server deletes the user's row; the teammate's row is untouched. - remote.initialSnapshot = serverSnapshot(page, [theirs], 3); - publishAcks(sent, 2); + send(remote, page, sent, contentRevision: 3); await settleSync(container, 3); + expect(server.row(first.id).deleted, isTrue); + expect(server.row('their-link').revision, 1); // Their lineup is drawn whole, on the origin from its own row. final graph = container.read(lineUpProvider).graph; @@ -5357,8 +5666,7 @@ void main() { () async { final (container, remote, page) = await openEmpty(); final first = place(container); - final landed = await land(container, remote, page, - previous: const [], revision: 1, contentRevision: 2); + await land(container, remote, page, contentRevision: 2); // The user starts a new lineup from the landed origin... final lineUps = container.read(lineUpProvider.notifier) @@ -5369,22 +5677,8 @@ void main() { position: const Offset(500, 500), )); // ...while a teammate deletes that lineup, origin included. - remote.setSnapshot(serverSnapshot( - page, - [ - for (final row in landed) - RemoteLineup( - publicId: row.publicId, - strategyPublicId: row.strategyPublicId, - pagePublicId: row.pagePublicId, - payload: row.payload, - sortIndex: row.sortIndex, - revision: 2, - deleted: true, - ), - ], - 3, - )); + server.teammateDelete(first.id); + remote.setSnapshot(serverSnapshot(page, server.rows, 3)); await _settle(); // The teammate's delete stands: the user never re-sends it. expect( @@ -5404,8 +5698,9 @@ void main() { ); // The server holds the new lineup whole, and it loads back. - await land(container, remote, page, - previous: const [], revision: 3, contentRevision: 4); + final stored = await land(container, remote, page, contentRevision: 4); + expect(stored.map((row) => row.publicId), [second.id]); + expect(server.row(first.id).deleted, isTrue); final hydrated = container.read(lineUpProvider).graph; expect(hydrated.links.map((link) => link.id), [second.id]); expect(hydrated.origins.map((origin) => origin.id), [first.originId]); @@ -5416,16 +5711,11 @@ void main() { () async { final (container, remote, page) = await openEmpty(); final link = place(container); - var revision = 1; var contentRevision = 2; var lineups = await land(container, remote, page, - previous: const [], - revision: revision++, contentRevision: contentRevision++); Future landNext() async { lineups = await land(container, remote, page, - previous: lineups, - revision: revision++, contentRevision: contentRevision++); } @@ -5495,17 +5785,14 @@ void main() { test('undoing a notes edit keeps an image a teammate added', () async { final (container, remote, page) = await openEmpty(); final link = place(container); - var lineups = await land(container, remote, page, - previous: const [], revision: 1, contentRevision: 2); + await land(container, remote, page, contentRevision: 2); container .read(lineUpProvider.notifier) .updateLink(link.copyWith(notes: 'jump throw')); - lineups = await land( + await land( container, remote, page, - previous: lineups, - revision: 2, contentRevision: 3, teammate: (data) { data['images'] = [ @@ -5552,8 +5839,7 @@ void main() { final placed = place(container); final lineUps = container.read(lineUpProvider.notifier); lineUps.updateLink(placed.copyWith(images: images(['a', 'b', 'c', 'd']))); - var lineups = await land(container, remote, page, - previous: const [], revision: 1, contentRevision: 2); + await land(container, remote, page, contentRevision: 2); // Remove the first image, then two from the middle. lineUps.updateLink(container .read(lineUpProvider) @@ -5565,8 +5851,7 @@ void main() { .links .single .copyWith(images: images(['d']))); - lineups = await land(container, remote, page, - previous: lineups, revision: 2, contentRevision: 3); + await land(container, remote, page, contentRevision: 3); final history = container.read(actionProvider.notifier); history.undoAction(); @@ -5596,20 +5881,17 @@ void main() { final placed = place(container); final lineUps = container.read(lineUpProvider.notifier); lineUps.updateLink(placed.copyWith(images: images(['a', 'b', 'c']))); - var lineups = await land(container, remote, page, - previous: const [], revision: 1, contentRevision: 2); + await land(container, remote, page, contentRevision: 2); lineUps.updateLink(container .read(lineUpProvider) .links .single .copyWith(images: images(['a', 'c']))); // Our removal of b lands, and a teammate adds x right after a. - lineups = await land( + await land( container, remote, page, - previous: lineups, - revision: 2, contentRevision: 3, teammate: (data) { (data['images'] as List) @@ -5631,24 +5913,23 @@ void main() { test('undoing a visibility toggle keeps a teammate toggle', () async { final (container, remote, page) = await openEmpty(); final link = place(container); - var lineups = await land(container, remote, page, - previous: const [], revision: 1, contentRevision: 2); + await land(container, remote, page, contentRevision: 2); final landing = container.read(lineUpProvider).landings.single; container.read(lineUpProvider.notifier).updateLandingAbilityVisualState( landingId: link.landingId, visualState: landing.ability.visualState.copyWith(showRangeOutline: false), ); - lineups = await land( + await land( container, remote, page, - previous: lineups, - revision: 2, contentRevision: 3, teammate: (data) { - final ability = (data['landing'] as Map)['ability'] as Map; + final landing = data['landing'] as Map; + final ability = landing['ability'] as Map; (ability['visualState'] as Map)['showRangeFill'] = false; + _bumpVersion(landing); }, ); @@ -5668,22 +5949,21 @@ void main() { () async { final (container, remote, page) = await openEmpty(); final link = place(container); - var lineups = await land(container, remote, page, - previous: const [], revision: 1, contentRevision: 2); + await land(container, remote, page, contentRevision: 2); const moved = Offset(500, 500); container .read(lineUpProvider.notifier) .updateOriginAgentPosition(link.originId, moved); // Our move lands and, in the same server state, a teammate arms it. - lineups = await land( + await land( container, remote, page, - previous: lineups, - revision: 2, contentRevision: 3, teammate: (data) { - ((data['origin'] as Map)['agent'] as Map)['weapon'] = 'vandal'; + final origin = data['origin'] as Map; + (origin['agent'] as Map)['weapon'] = 'vandal'; + _bumpVersion(origin); }, ); @@ -5711,6 +5991,7 @@ void main() { }, ); queue = _FakeStrategyOpQueueNotifier(); + server = _FakeServer(first.publicId); final container = await _cloudContainer(remote: remote, queue: queue); final session = container.read(strategyPageSessionProvider.notifier); await session.initializeForStrategy( @@ -5721,11 +6002,11 @@ void main() { final link = place(container); await _settle(); var ops = queuedOps(); - var lineups = apply(ops.values, first, revision: 1, previous: const []); + var acks = server.applyBatch(ops.values); remote.initialSnapshot = - serverSnapshot(first, lineups, 2, pages: [first, second]); + serverSnapshot(first, server.rows, 2, pages: [first, second]); remote.pageCatalog[first.publicId] = remote.initialSnapshot.activePage!; - publishAcks(ops, 1); + queue.answer(ops, acks); await settleSync(container, 2); const moved = Offset(500, 500); @@ -5740,26 +6021,23 @@ void main() { // on screen. ops = queuedOps(); expect(ops.keys.where((key) => key.pageId == first.publicId), isNotEmpty); - lineups = apply( - ops.values, - first, - revision: 2, - previous: lineups, - teammate: (data) { - ((data['origin'] as Map)['agent'] as Map)['weapon'] = 'vandal'; - }, - ); + acks = server.applyBatch(ops.values); + server.teammateEdit(link.id, (data) { + final origin = data['origin'] as Map; + (origin['agent'] as Map)['weapon'] = 'vandal'; + _bumpVersion(origin); + }); remote.pageCatalog[first.publicId] = _pageSnapshot( first, settings: settingsFor(3), contentRevision: 3, - lineups: lineups, + lineups: server.rows, ); remote.initialSnapshot = _editorSnapshot( pages: [first, second], activePage: secondSnapshot, ); - publishAcks(ops, 2); + queue.answer(ops, acks); await settleSync(container, 9); await session.setActivePage(first.publicId); @@ -5774,6 +6052,492 @@ void main() { ); await _settle(); }); + + /// Places lineups A and B from one origin, then C from it too, the way + /// the editor does. + List placeFanOutOfThree(ProviderContainer container) { + final (a, b) = placeFanOut(container); + final lineUps = container.read(lineUpProvider.notifier) + ..startFromOrigin(a.originId); + lineUps.setDraftAbility(PlacedAbility( + id: 'ability-z', + data: AgentData.agents[AgentType.sova]!.abilities[2], + position: const Offset(600, 450), + )); + final c = lineUps.commitPlacement(name: 'Deep')!; + return [a, b, c]; + } + + test( + 'a shared spot moved on only some rows stays queued for the rest ' + 'until it lands', () async { + final (container, remote, page) = await openEmpty(); + final [a, b, c] = placeFanOutOfThree(container); + await land(container, remote, page, contentRevision: 2); + expect( + {for (final row in server.liveRows) row.publicId: row.revision}, + {a.id: 1, b.id: 1, c.id: 1}, + ); + + const moved = Offset(500, 500); + container + .read(lineUpProvider.notifier) + .updateOriginAgentPosition(a.originId, moved); + await _settle(); + final sent = lineupOps(queuedOps()); + final cKey = keyOf(page, c.id); + expect(sent.keys.toSet(), {keyOf(page, a.id), keyOf(page, b.id), cKey}); + + // The server takes A's and B's patches; C's send fails, so it waits in + // the queue while the session refreshes from the server. + send(remote, page, {...sent}..remove(cKey), contentRevision: 3); + for (var i = 0; i < 10; i++) { + await _settle(); + } + // The server draws the moved origin for all three lineups now (A's and + // B's copies outrank C's), but C's row still holds the old one. + final drawn = lineUpGraphFromRemoteLineups(server.liveRows); + expect(drawn.origins.single.agent.position, moved); + expect(_endsOf(server.row(c.id)).originVersion, 1); + + // The user carries on, so the page syncs against that snapshot. C's + // patch is not taken as done: it stays queued, as it was. + container.read(lineUpProvider.notifier).updateLink(container + .read(lineUpProvider) + .linkById(a.id)! + .copyWith(notes: 'next')); + await _settle(); + final waiting = lineupOps(queuedOps()); + expect(waiting.keys.toSet(), {keyOf(page, a.id), cKey}); + final patch = waiting[cKey]! as LineupPatchOp; + expect(patch.expectedLineupRevision, 1); + final origin = cloudPayloadData(patch.payload)['origin'] as Map; + expect(origin['version'], 2); + expect((origin['agent'] as Map)['position'], + {'dx': moved.dx, 'dy': moved.dy}); + + // Its retry lands: every row holds the moved origin, at its version. + await land(container, remote, page, contentRevision: 4); + expect( + {for (final row in server.liveRows) row.publicId: row.revision}, + {a.id: 3, b.id: 2, c.id: 2}, + ); + for (final row in server.liveRows) { + expect(_endsOf(row).originVersion, 2, reason: row.publicId); + final data = cloudPayloadData(row.payload); + expect(((data['origin'] as Map)['agent'] as Map)['position'], + {'dx': moved.dx, 'dy': moved.dy}, + reason: row.publicId); + } + // Three adds, three patches of the origin, and the notes; nothing more. + expect(server.received, hasLength(7)); + expect(lineupOps(queuedOps()), isEmpty); + expect(desiredOps(container, page), isEmpty); + expect(queue.state.attentionByEntityKey, isEmpty); + await _settle(); + }); + + const newer = Offset(330, 330); + const older = Offset(220, 220); + + /// Lineups A and B into one landing whose rows disagree: A's copy is at + /// [newer], version 5; B's at [older], version 3. B's row has the higher + /// revision, which ranks nothing. + List divergedFanIn(String pageId) => [ + _lineupRow(pageId, 'link-a', + origin: _originJson('origin-a'), + landing: _landingJson('landing', newer, 5), + revision: 1), + _lineupRow(pageId, 'link-b', + origin: _originJson('origin-b', const Offset(60, 20)), + landing: _landingJson('landing', older, 3), + revision: 7, + sortIndex: 1), + ]; + + /// The landing the server's rows draw, as anyone opening the page sees. + Offset serverLanding() => lineUpGraphFromRemoteLineups(server.liveRows) + .landings + .single + .ability + .position; + + void expectHealOfB(StrategyOp? op) { + expect(op, isA()); + final heal = op! as LineupPatchOp; + expect(heal.expectedLineupRevision, 7); + final landing = cloudPayloadData(heal.payload)['landing'] as Map; + expect(landing['version'], 5); + expect((landing['ability'] as Map)['position'], + {'dx': newer.dx, 'dy': newer.dy}); + } + + test( + 'opening a page heals a row holding an older copy, once, so deleting ' + 'the lineup with the newer one keeps the spot', () async { + final (container, remote, page) = + await openEmpty(divergedFanIn('page-1')); + LineUpLanding landing() => + container.read(lineUpProvider).landingById('landing')!; + expect(landing().ability.position, newer); + + // Hydration sends B's row the drawn copy at once, against B's revision. + await _settle(); + final heals = lineupOps(queuedOps()); + expect(heals.keys, [keyOf(page, 'link-b')]); + expectHealOfB(heals.values.single); + await land(container, remote, page, contentRevision: 2); + expect(server.row('link-b').revision, 8); + expect(_endsOf(server.row('link-b')).landingVersion, 5); + expect(server.received, hasLength(1)); + + // Once: the healed page syncs and hydrates again with nothing to send. + await container.read(strategyProvider.notifier).notifyCloudMutation(); + await _settle(); + expect(lineupOps(queuedOps()), isEmpty); + + // Deleting A leaves B on the newer copy, here and for anyone opening + // the page. + container.read(lineUpProvider.notifier).deleteLink('link-a'); + await land(container, remote, page, contentRevision: 3); + expect(server.row('link-a').deleted, isTrue); + expect(server.received, hasLength(2)); + expect(serverLanding(), newer); + expect(landing().ability.position, newer); + expect(lineupOps(queuedOps()), isEmpty); + await _settle(); + }); + + test( + 'deleting the lineup with the newer copy while the heal waits sends ' + 'both in one batch', () async { + final (container, remote, page) = + await openEmpty(divergedFanIn('page-1')); + await _settle(); + expect(lineupOps(queuedOps()).keys, [keyOf(page, 'link-b')]); + + container.read(lineUpProvider.notifier).deleteLink('link-a'); + await _settle(); + final batch = lineupOps(queuedOps()); + expect( + batch.keys.toSet(), {keyOf(page, 'link-a'), keyOf(page, 'link-b')}); + expect(batch[keyOf(page, 'link-a')], isA()); + expectHealOfB(batch[keyOf(page, 'link-b')]); + + await land(container, remote, page, contentRevision: 2); + expect(server.row('link-a').deleted, isTrue); + expect(_endsOf(server.row('link-b')).landingVersion, 5); + expect(serverLanding(), newer); + expect( + container.read(lineUpProvider).landingById('landing')!.ability.position, + newer, + ); + expect(lineupOps(queuedOps()), isEmpty); + await _settle(); + }); + + test('opening a page whose copies agree writes no lineup', () async { + final (container, remote, page) = await openEmpty([ + _lineupRow('page-1', 'link-a', + origin: _originJson('origin-a'), + landing: _landingJson('landing', newer, 4), + revision: 3), + _lineupRow('page-1', 'link-b', + origin: _originJson('origin-b', const Offset(60, 20)), + landing: _landingJson('landing', newer, 4), + revision: 8, + sortIndex: 1), + ]); + await _settle(); + expect(lineupOps(queuedOps()), isEmpty); + + // Nor does the next hydration. + remote.setSnapshot(serverSnapshot(page, server.rows, 2)); + await settleSync(container, 2); + expect(lineupOps(queuedOps()), isEmpty); + expect(server.received, isEmpty); + }); + + test('opening a diverged page heals each stale row once', () async { + final (container, remote, page) = await openEmpty([ + ...divergedFanIn('page-1'), + _lineupRow('page-1', 'link-c', + origin: _originJson('origin-c', const Offset(90, 20)), + landing: _landingJson('landing', older, 3), + revision: 4, + sortIndex: 2), + _lineupRow('page-1', 'link-d', + origin: _originJson('origin-d', const Offset(10, 90)), + landing: _landingJson('landing-d'), + sortIndex: 3), + ]); + await _settle(); + final heals = lineupOps(queuedOps()); + expect( + heals.keys.toSet(), {keyOf(page, 'link-b'), keyOf(page, 'link-c')}); + expect( + { + for (final op in heals.values.cast()) + op.lineupPublicId: op.expectedLineupRevision, + }, + {'link-b': 7, 'link-c': 4}, + ); + await land(container, remote, page, contentRevision: 2); + expect(server.received, hasLength(2)); + expect( + {for (final row in server.liveRows) row.publicId: row.revision}, + {'link-a': 1, 'link-b': 8, 'link-c': 5, 'link-d': 1}, + ); + + // A teammate's unrelated change brings a fresh hydration: nothing more + // is written. + server.teammateEdit('link-d', (data) => data['notes'] = 'teammate'); + remote.setSnapshot(serverSnapshot(page, server.rows, 3)); + await settleSync(container, 3); + expect( + container.read(lineUpProvider).linkById('link-d')!.notes, 'teammate'); + expect(lineupOps(queuedOps()), isEmpty); + expect(server.received, hasLength(2)); + }); + + test('two teammates healing the same row both land, the second as a noop', + () async { + final (container, remote, page) = + await openEmpty(divergedFanIn('page-1')); + final mine = queue; + // A teammate opens the page before either heal has landed. + final theirRemote = _FakeRemoteEditorNotifier(serverSnapshot( + page, + server.rows, + 1, + )); + final theirQueue = _FakeStrategyOpQueueNotifier(); + final theirs = + await _cloudContainer(remote: theirRemote, queue: theirQueue); + await theirs + .read(strategyPageSessionProvider.notifier) + .initializeForStrategy( + strategyId: 'cloud-strategy', + source: StrategySource.cloud, + selectFirstPageIfNeeded: true, + ); + await _settle(); + Map healsOf( + _FakeStrategyOpQueueNotifier queue) => + lineupOps({ + for (final entry in queue.state.queuedByEntityKey.entries) + entry.key: entry.value.pending.op, + }); + final theirHeals = healsOf(theirQueue); + expect(healsOf(mine).keys, [keyOf(page, 'link-b')]); + expect(theirHeals.keys, [keyOf(page, 'link-b')]); + expectHealOfB(theirHeals.values.single); + + // Mine lands first. + await land(container, remote, page, contentRevision: 2); + expect(server.row('link-b').revision, 8); + + // Theirs, sent against the revision before mine, finds the row + // already holding it: a noop, not a conflict. + final theirSent = { + for (final entry in theirQueue.state.queuedByEntityKey.entries) + entry.key: entry.value.pending.op, + }; + final theirAcks = server.applyBatch(theirSent.values); + expect( + theirAcks + .firstWhere((ack) => ack.opId == theirHeals.values.single.opId), + isA().having((ack) => ack.currentRevision, 'revision', 8), + ); + theirRemote.initialSnapshot = serverSnapshot(page, server.rows, 2); + theirQueue.answer(theirSent, theirAcks); + for (var i = 0; i < 10; i++) { + await _settle(); + } + expect(theirs.read(strategySettingsProvider).agentSize, 32, + reason: 'their page rehydrated from the server'); + for (final (client, clientQueue) in [ + (container, mine), + (theirs, theirQueue), + ]) { + expect(clientQueue.state.attentionByEntityKey, isEmpty); + expect(client.read(strategyConflictProvider), isEmpty); + expect(healsOf(clientQueue), isEmpty); + expect( + client.read(lineUpProvider).landingById('landing')!.ability.position, + newer, + ); + } + expect(server.row('link-b').revision, 8); + expect(server.received, hasLength(2)); + }); + + /// Opens [page] on the real queue, which sends to [server] and reads it + /// back after every batch. Returns the container and the batches sent. + Future<(ProviderContainer, List>)> openOnRealQueue( + RemotePage page, + ) async { + RemoteEditorSnapshot read() => _editorSnapshot( + pages: [page], + activePage: _pageSnapshot( + page, + settings: settingsFor(1), + elements: server.elements, + lineups: server.rows, + ), + ); + final remote = _FakeRemoteEditorNotifier(read()); + final repository = _ServerRepository( + server, + afterBatch: () => remote.initialSnapshot = read(), + ); + final container = ProviderContainer(overrides: [ + remoteEditorSnapshotProvider.overrideWith(() => remote), + durableStrategyOutboxStoreProvider + .overrideWithValue(MemoryDurableStrategyOutboxStore()), + convexStrategyRepositoryProvider.overrideWithValue(repository), + authProvider.overrideWith(_CloudReadyAuthProvider.new), + convexConnectionSnapshotProvider.overrideWithValue(true), + cloudMediaAccountIdProvider.overrideWithValue('account-a'), + cloudMediaUploadQueueProvider + .overrideWith(() => _RecordingMediaQueue()), + ]); + addTearDown(container.dispose); + container.listen(strategyPageSessionProvider, (_, __) {}); + await container.read(remoteEditorSnapshotProvider.future); + container + .read(strategyOpQueueProvider.notifier) + .setActiveStrategy('cloud-strategy', accountId: 'account-a'); + container + .read(strategyProvider.notifier) + .setFromState(const StrategyState( + strategyId: 'cloud-strategy', + strategyName: 'Cloud Strategy', + source: StrategySource.cloud, + storageDirectory: null, + isOpen: true, + )); + await container + .read(strategyPageSessionProvider.notifier) + .initializeForStrategy( + strategyId: 'cloud-strategy', + source: StrategySource.cloud, + selectFirstPageIfNeeded: true, + ); + return (container, repository.batches); + } + + /// The edit to [key] the server refused as deleted waits in attention, + /// with its conflict; Keep mine then sends it again. Returns the op it + /// sent, once it has landed. + Future refusedThenKeptMine( + ProviderContainer container, + List> batches, + EntitySyncKey key, + ) async { + StrategyOpQueueState queueState() => + container.read(strategyOpQueueProvider); + List sentFor() => [ + for (final batch in batches) + ...batch.where((op) => EntitySyncKey.forStrategyOp(op) == key), + ]; + await _until(() => queueState().attentionByEntityKey.containsKey(key)); + for (var i = 0; i < 10; i++) { + await _settle(); + } + expect(sentFor().single.kind, StrategyOpKind.patch); + expect( + queueState() + .lastAckBatch + .singleWhere((acked) => acked.entityKey == key) + .ack, + isA().having( + (ack) => ack.rejectionReason, 'reason', OpRejectionReason.deleted), + ); + expect(container.read(strategyConflictProvider), hasLength(1)); + + final opQueue = container.read(strategyOpQueueProvider.notifier); + await opQueue.retryRejected(flushImmediately: false); + await opQueue.flushNow(); + await _until(() => queueState().pending.isEmpty && sentFor().length == 2); + for (var i = 0; i < 10; i++) { + await _settle(); + } + expect(queueState().attentionByEntityKey, isEmpty); + expect(queueState().needsAttention, isFalse); + return sentFor().last; + } + + test( + 'Keep mine restores a lineup a teammate deleted while the user ' + 'edited it', () async { + final page = _page('page-1', 0); + server = + _FakeServer(page.publicId, lineups: [_lineup(page.publicId, 'a')]); + final (container, batches) = await openOnRealQueue(page); + LineUpLink? onScreen() => + container.read(lineUpProvider).linkById('link-a'); + expect(onScreen(), isNotNull); + + // A teammate deletes the lineup; this canvas has not read that yet. + server.teammateDelete('link-a'); + expect(server.row('link-a').revision, 2); + + // The user edits its notes. The server refuses the change to the + // tombstone rather than store an edit nobody would see; the user still + // sees their work. + container + .read(lineUpProvider.notifier) + .updateLink(onScreen()!.copyWith(notes: 'mine')); + final restore = await refusedThenKeptMine( + container, batches, _lineupKey(page.publicId, 'a')); + + // Keep mine brought it back over the tombstone, as the user has it. + expect(restore, isA()); + expect((restore as LineupAddOp).expectedLineupRevision, 2); + expect(cloudPayloadData(restore.payload)['notes'], 'mine'); + final row = server.row('link-a'); + expect(row.deleted, isFalse); + expect(row.revision, 3); + expect(cloudPayloadData(row.payload)['notes'], 'mine'); + expect(lineUpGraphFromRemoteLineups(server.liveRows).links.single.notes, + 'mine'); + expect(onScreen()?.notes, 'mine'); + }); + + test( + 'Keep mine restores an element a teammate deleted while the user ' + 'moved it', () async { + final page = _page('page-1', 0); + server = _FakeServer(page.publicId, + elements: [_textElement(page.publicId, 'text-a', 'hello')]); + final (container, batches) = await openOnRealQueue(page); + PlacedText? onScreen() => container + .read(textProvider) + .where((text) => text.id == 'text-a') + .firstOrNull; + expect(onScreen(), isNotNull); + + // A teammate deletes the text; this canvas has not read that yet. + server.teammateDeleteElement('text-a'); + + // The user moves it. + container + .read(textProvider.notifier) + .updatePosition(const Offset(300, 300), 'text-a'); + final restore = await refusedThenKeptMine( + container, batches, EntitySyncKey.element(page.publicId, 'text-a')); + + // Keep mine brought it back over the tombstone, where the user put it. + expect(restore, isA()); + expect((restore as ElementAddOp).expectedElementRevision, 2); + final element = server.element('text-a'); + expect(element.deleted, isFalse); + expect(element.revision, 3); + expect(element.payload, restore.payload); + expect(onScreen()?.position, const Offset(300, 300)); + expect(onScreen()?.text, 'hello'); + }); }); group('lineup history across rehydration', () { diff --git a/tool/convex_client_gauntlet/runtime/lib/workload.dart b/tool/convex_client_gauntlet/runtime/lib/workload.dart index 37130caa..4bb8e864 100644 --- a/tool/convex_client_gauntlet/runtime/lib/workload.dart +++ b/tool/convex_client_gauntlet/runtime/lib/workload.dart @@ -285,6 +285,7 @@ Map _lineupPayload(int seed, int cycle, int variant) { 'kind': 'plain', 'lineUpID': '$id-origin', }, + 'version': 1, }, 'landing': { 'id': '$id-landing', @@ -298,6 +299,7 @@ Map _lineupPayload(int seed, int cycle, int variant) { 'length': 0, 'lineUpID': '$id-landing', }, + 'version': 1, }, }, }; From 5b0ecd66d62c19611224fdb87819ff78d84d0e67 Mon Sep 17 00:00:00 2001 From: Dara Adedeji Date: Fri, 2 Oct 2026 11:44:04 -0400 Subject: [PATCH 05/14] Drop lineup spot versions; answer replays where they landed; summarize agents from a side table A shared spot no longer carries a version. Each lineup row owns its copy of its spots and only ever changes when the user changes that lineup or its spot, so the server stores what it is sent and joins nothing. An accepted op whose answer was lost is now replayed as a noop at the revision it actually landed at, not the row's latest. Answering with the latest let a waiting successor rebase onto a teammate's later edit and overwrite it without a conflict. The strategy agent summary reads a small lineupAgents table, kept in step on every accepted lineup op, duplicate and purge, instead of every lineup payload, so its reads stay small however large lineups get. Production and dev held no lineup rows when this shape shipped (2026-10-01), so nothing needs backfilling. Co-Authored-By: Claude Opus 5.5 (1M context) --- convex/lib/strategyAgentSummary.ts | 96 +++++-- convex/lineups.test.ts | 426 ++++++++++++++++++++++++----- convex/maintenance.ts | 12 +- convex/mediaLimits.test.ts | 81 ++++++ convex/ops.ts | 26 +- convex/pageTrash.test.ts | 10 + convex/schema.ts | 17 +- convex/strategies.ts | 21 +- convex/strategyDuplicate.test.ts | 218 +++++++-------- convex/syncBoundaries.test.ts | 64 +++++ convex/testContent.helpers.ts | 14 +- 11 files changed, 753 insertions(+), 232 deletions(-) diff --git a/convex/lib/strategyAgentSummary.ts b/convex/lib/strategyAgentSummary.ts index d0242a1e..36947cf8 100644 --- a/convex/lib/strategyAgentSummary.ts +++ b/convex/lib/strategyAgentSummary.ts @@ -14,30 +14,84 @@ function agentTypeOf(data: unknown): string | null { return typeof type === "string" && type.length > 0 ? type : null; } +/// The agent a lineup starts from: its origin's id and agent type, or null +/// for a lineup without one. +export function lineupAgentOf( + payload: Doc<"lineups">["payload"], +): { originId: string; agentType: string } | null { + const origin = asRecord(payload.data.origin); + const agentType = agentTypeOf(origin?.agent); + if (origin === null || typeof origin.id !== "string" || agentType === null) { + return null; + } + return { originId: origin.id, agentType }; +} + +type LineupAgent = Pick< + Doc<"lineupAgents">, + "pageId" | "originId" | "agentType" +>; + +/// Brings the lineupAgents row of one lineup in step with it: one row while +/// the lineup is live and starts from an agent, none otherwise. Call after +/// every write of a lineup row, with null once the row itself is gone. +export async function syncLineupAgent( + ctx: MutationCtx, + lineupId: Id<"lineups">, + lineup: Pick< + Doc<"lineups">, + "strategyId" | "pageId" | "deleted" | "payload" + > | null, +): Promise { + const existing = await ctx.db + .query("lineupAgents") + .withIndex("by_lineupId", (q) => q.eq("lineupId", lineupId)) + .unique(); + const agent = + lineup === null || lineup.deleted ? null : lineupAgentOf(lineup.payload); + if (agent === null || lineup === null) { + if (existing !== null) await ctx.db.delete(existing._id); + return; + } + const wanted = { + strategyId: lineup.strategyId, + pageId: lineup.pageId, + lineupId, + ...agent, + }; + if (existing === null) { + await ctx.db.insert("lineupAgents", wanted); + } else if ( + existing.strategyId !== wanted.strategyId || + existing.pageId !== wanted.pageId || + existing.originId !== wanted.originId || + existing.agentType !== wanted.agentType + ) { + await ctx.db.patch(existing._id, wanted); + } +} + /// Recomputes which agents a strategy uses, from its live agent elements and /// the origins of its live lineups on pages not in the trash, and stores the -/// answer in its own row. Content ops never -/// touch the strategy row itself; the summary is derived data that the -/// folder tree reads without scanning elements. +/// answer in its own row. Content ops never touch the strategy row itself; +/// the summary is derived data that the folder tree reads without scanning +/// elements. export async function refreshStrategyAgentSummary( ctx: MutationCtx, strategyId: Id<"strategies">, ): Promise { - // Only agent elements and live lineups carry an agent, so only they are - // read. That still reads every live lineup row, image ids and all (never - // image bytes), so callers refresh only when a change may have touched an - // agent: applyBatch skips batches without page, agent or lineup changes. + // Agent elements are small. Lineups are read through their lineupAgents + // rows, never the lineup rows themselves: those carry image lists and + // could take a strategy's reads past a transaction's limits. const agents = await ctx.db .query("elements") .withIndex("by_strategyId_and_elementType", (q) => q.eq("strategyId", strategyId).eq("elementType", "agent"), ) .collect(); - const lineups = await ctx.db - .query("lineups") - .withIndex("by_strategyId_and_deleted", (q) => - q.eq("strategyId", strategyId).eq("deleted", false), - ) + const lineupAgents = await ctx.db + .query("lineupAgents") + .withIndex("by_strategyId", (q) => q.eq("strategyId", strategyId)) .collect(); const trashedPageIds = new Set( ( @@ -54,16 +108,16 @@ export async function refreshStrategyAgentSummary( await storeStrategyAgentSummary( ctx, strategyId, - agentTypesOf(agents.filter(onLivePage), lineups.filter(onLivePage)), + agentTypesOf(agents.filter(onLivePage), lineupAgents.filter(onLivePage)), ); } /// The agents that live content uses, most used first. Takes any elements -/// and lineup rows; live agent elements count, and each origin of a live -/// lineup once per page, however many lineups share it. +/// and the agents of live lineups; live agent elements count, and each +/// lineup origin once per page, however many lineups share it. export function agentTypesOf( elements: Pick, "deleted" | "elementType" | "payload">[], - lineups: Pick, "deleted" | "pageId" | "payload">[], + lineupAgents: LineupAgent[], ): string[] { const counts = new Map(); const bump = (type: string | null) => { @@ -75,13 +129,11 @@ export function agentTypesOf( bump(agentTypeOf(element.payload.data)); } const countedOrigins = new Set(); - for (const lineup of lineups) { - if (lineup.deleted) continue; - const origin = asRecord(lineup.payload.data.origin); - const originKey = `${lineup.pageId}:${String(origin?.id)}`; - if (origin === null || countedOrigins.has(originKey)) continue; + for (const { pageId, originId, agentType } of lineupAgents) { + const originKey = `${pageId}:${originId}`; + if (countedOrigins.has(originKey)) continue; countedOrigins.add(originKey); - bump(agentTypeOf(origin.agent)); + bump(agentType); } return [...counts.entries()] .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])) diff --git a/convex/lineups.test.ts b/convex/lineups.test.ts index dc4f6464..4634e5bd 100644 --- a/convex/lineups.test.ts +++ b/convex/lineups.test.ts @@ -4,7 +4,7 @@ import { type TestConvexForDataModelAndIdentity, } from "convex-test"; import { makeFunctionReference } from "convex/server"; -import { beforeAll, describe, expect, test } from "vitest"; +import { afterEach, beforeAll, describe, expect, test, vi } from "vitest"; import type { DataModel } from "./_generated/dataModel"; import { markAssetReferencesReady } from "./lib/assetReferences"; import { CURRENT_CLOUD_PROTOCOL_VERSION } from "./lib/cloudProtocol"; @@ -36,6 +36,13 @@ const listForStrategy = makeFunctionReference<"query">( const listReferencedAssetIds = makeFunctionReference<"query">( "images:listReferencedAssetIds", ); +const duplicateStrategy = makeFunctionReference<"mutation">( + "strategies:duplicate", +); +const restorePage = makeFunctionReference<"mutation">("pages:restore"); +const purgeOldTombstones = makeFunctionReference<"mutation">( + "maintenance:purgeOldTombstones", +); type Harness = TestConvexForDataModel; type RootHarness = TestConvexForDataModelAndIdentity; @@ -189,20 +196,79 @@ async function addSecondPage(owner: Harness, publicId: string) { }); } -async function agentSummary(t: RootHarness): Promise { +async function agentSummary( + t: RootHarness, + strategy = strategyPublicId, +): Promise { return await t.run(async (ctx) => { - const strategy = await ctx.db + const row = await ctx.db .query("strategies") - .withIndex("by_publicId", (q) => q.eq("publicId", strategyPublicId)) + .withIndex("by_publicId", (q) => q.eq("publicId", strategy)) .unique(); const summary = await ctx.db .query("strategyAgentSummaries") - .withIndex("by_strategyId", (q) => q.eq("strategyId", strategy!._id)) + .withIndex("by_strategyId", (q) => q.eq("strategyId", row!._id)) .unique(); return summary?.agentTypes ?? []; }); } +type LineupAgentRow = { + lineup: string; + page: string; + originId: string; + agentType: string; +}; + +/// A strategy's lineupAgents rows, each named by its lineup's and page's +/// public ids, ordered by lineup. Fails on a row that names a lineup gone +/// or out of step with it (another strategy or page). +async function lineupAgentRows( + t: RootHarness, + strategy = strategyPublicId, +): Promise { + return await t.run(async (ctx) => { + const strategyRow = await ctx.db + .query("strategies") + .withIndex("by_publicId", (q) => q.eq("publicId", strategy)) + .unique(); + const rows = await ctx.db + .query("lineupAgents") + .withIndex("by_strategyId", (q) => q.eq("strategyId", strategyRow!._id)) + .collect(); + const described: LineupAgentRow[] = []; + for (const row of rows) { + const lineup = await ctx.db.get(row.lineupId); + const page = await ctx.db.get(row.pageId); + if ( + lineup === null || + page === null || + lineup.strategyId !== row.strategyId || + lineup.pageId !== row.pageId + ) { + throw new Error(`lineupAgents row ${row._id} is out of step`); + } + described.push({ + lineup: lineup.publicId, + page: page.publicId, + originId: row.originId, + agentType: row.agentType, + }); + } + return described.sort((a, b) => a.lineup.localeCompare(b.lineup)); + }); +} + +async function strategyRevision(t: RootHarness): Promise { + return await t.run(async (ctx) => { + const strategy = await ctx.db + .query("strategies") + .withIndex("by_publicId", (q) => q.eq("publicId", strategyPublicId)) + .unique(); + return strategy!.revision; + }); +} + beforeAll(() => { // Page snapshots serialize asset URLs from the R2 public base. process.env.R2_ACCOUNT_ID = "lineups-account"; @@ -213,6 +279,10 @@ beforeAll(() => { process.env.R2_S3_ENDPOINT = "https://lineups.r2.test"; }); +afterEach(() => { + vi.useRealTimers(); +}); + /** Two lineups from different origins aiming at one shared landing. */ const fanIn = [ lineup("from-heaven", { @@ -625,72 +695,6 @@ describe("a lineup row must be drawable on its own", () => { expect(await pageLineups(owner)).toEqual([]); }); - test("an end without a whole-number spot version of at least 1 is refused", async () => { - const { owner } = await createHarness(); - const valid = lineupPayload("k"); - // Each case puts [version] on one end (undefined drops the field). - const cases: Array<["origin" | "landing", unknown]> = []; - for (const end of ["origin", "landing"] as const) { - for (const version of [undefined, 0, -1, 1.5, "2", null]) { - cases.push([end, version]); - } - } - const ops = cases.map(([end, version], index) => { - const id = `bad-version-${index}`; - const payload = lineupPayload(id); - const withVersion: Record = { ...payload.data[end] }; - if (version === undefined) { - delete withVersion.version; - } else { - withVersion.version = version; - } - return { - ...addOp(lineup(id), index), - payload: { ...payload, data: { ...payload.data, [end]: withVersion } }, - }; - }); - const results = await apply(owner, "owner-client", [ - ...ops, - addOp({ key: "k", payload: valid }, cases.length), - ]); - expect(results).toHaveLength(cases.length + 1); - for (const result of results.slice(0, cases.length)) { - expect(result).toMatchObject({ - status: "failed", - code: "INVALID_LINEUP_PAYLOAD_DATA", - message: "Lineup has no versioned origin or landing", - }); - } - expect(results[cases.length]).toMatchObject({ status: "applied" }); - expect(liveKeys(await pageLineups(owner))).toEqual(["k"]); - - // A patch that drops a version is refused too, leaving the row as it was. - const { version: _version, ...unversioned } = valid.data.landing; - const patched = await apply(owner, "owner-client", [ - { - ...patchOp("drop-version", { key: "k", payload: valid }, 1), - payload: { ...valid, data: { ...valid.data, landing: unversioned } }, - }, - ]); - expect(patched[0]).toMatchObject({ - status: "failed", - code: "INVALID_LINEUP_PAYLOAD_DATA", - }); - const row = (await pageLineups(owner))[0]!; - expect(row).toMatchObject({ revision: 1 }); - expect(row.payload).toEqual(valid); - }); - - test("each end's spot version is stored and read back as written", async () => { - const { owner } = await createHarness(); - const row = lineup("versioned", { originVersion: 4, landingVersion: 9 }); - await apply(owner, "owner-client", [addOp(row, 0)]); - const stored = (await pageLineups(owner))[0]!; - expect(stored.payload.data).toMatchObject({ - origin: { version: 4 }, - landing: { version: 9 }, - }); - }); }); describe("clients on protocol 4", () => { @@ -1065,6 +1069,286 @@ describe("agent summary", () => { ]); expect(await agentSummary(t)).toEqual(["astra", "kayo", "sova", "viper"]); }); + + test("each live lineup keeps one agent row in step with it, and the summary follows them", async () => { + const { t, owner } = await createHarness(); + const secondPage = "lineups-page-2"; + await addSecondPage(owner, secondPage); + const astra = (notes = "") => + lineup("astra-a", { originId: "astra", agentType: "astra", notes }); + const viper = (agentType: string) => + lineup("viper-a", { originId: "viper-1", agentType }); + const kayo = lineup("kayo-a", { originId: "kayo", agentType: "kayo" }); + + // Adding a lineup adds its row. + await apply(owner, "owner-client", [ + addOp(astra(), 0), + addOp(viper("viper"), 1), + addOp(kayo, 0, secondPage), + ]); + expect(await lineupAgentRows(t)).toEqual([ + { lineup: "astra-a", page: pagePublicId, originId: "astra", agentType: "astra" }, + { lineup: "kayo-a", page: secondPage, originId: "kayo", agentType: "kayo" }, + { lineup: "viper-a", page: pagePublicId, originId: "viper-1", agentType: "viper" }, + ]); + expect(await agentSummary(t)).toEqual(["astra", "kayo", "viper"]); + + // A patch that changes the origin's agent updates its row; one that + // leaves the agent alone leaves it as it was. + const astraRowBefore = await t.run(async (ctx) => + (await ctx.db.query("lineupAgents").collect()).find( + (row) => row.originId === "astra", + ), + ); + const patched = await apply(owner, "owner-client", [ + patchOp("viper-to-sova", viper("sova"), 1), + patchOp("astra-notes", astra("Run and throw"), 1), + ]); + expect(patched.map((result) => result.status)).toEqual([ + "applied", + "applied", + ]); + expect(await lineupAgentRows(t)).toEqual([ + { lineup: "astra-a", page: pagePublicId, originId: "astra", agentType: "astra" }, + { lineup: "kayo-a", page: secondPage, originId: "kayo", agentType: "kayo" }, + { lineup: "viper-a", page: pagePublicId, originId: "viper-1", agentType: "sova" }, + ]); + expect( + await t.run(async (ctx) => + (await ctx.db.query("lineupAgents").collect()).find( + (row) => row.originId === "astra", + ), + ), + ).toEqual(astraRowBefore); + expect(await agentSummary(t)).toEqual(["astra", "kayo", "sova"]); + + // Deleting a lineup removes its row; restoring it brings the row back. + await apply(owner, "owner-client", [deleteOp("delete-astra", "astra-a", 2)]); + expect((await lineupAgentRows(t)).map((row) => row.lineup)).toEqual([ + "kayo-a", + "viper-a", + ]); + expect(await agentSummary(t)).toEqual(["kayo", "sova"]); + const restored = await apply(owner, "owner-client", [ + { ...addOp(astra(), 0), opId: "restore-astra", expectedLineupRevision: 3 }, + ]); + expect(restored[0]).toMatchObject({ status: "applied", appliedRevision: 4 }); + expect(await lineupAgentRows(t)).toContainEqual({ + lineup: "astra-a", + page: pagePublicId, + originId: "astra", + agentType: "astra", + }); + expect(await agentSummary(t)).toEqual(["astra", "kayo", "sova"]); + + // A page in the trash keeps its lineups' rows for a restore, and the + // summary leaves them out until the page comes back. + const trashed = await apply(owner, "owner-client", [ + { + opId: "trash-page-2", + type: "page.delete", + pagePublicId: secondPage, + expectedStrategyRevision: await strategyRevision(t), + }, + ]); + expect(trashed[0]).toMatchObject({ status: "applied" }); + expect((await lineupAgentRows(t)).map((row) => row.lineup)).toEqual([ + "astra-a", + "kayo-a", + "viper-a", + ]); + expect(await agentSummary(t)).toEqual(["astra", "sova"]); + await owner.mutation(restorePage, { + clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, + strategyPublicId, + pagePublicId: secondPage, + }); + expect(await agentSummary(t)).toEqual(["astra", "kayo", "sova"]); + }); + + test("purging leaves no agent row behind, even one a tombstone kept", async () => { + vi.useFakeTimers(); + const { t, owner } = await createHarness(); + await t.run(markAssetReferencesReady); + await apply(owner, "owner-client", [ + addOp(lineup("gone", { agentType: "astra" }), 0), + addOp(lineup("kept", { agentType: "viper" }), 1), + ]); + await apply(owner, "owner-client", [deleteOp("delete-gone", "gone", 1)]); + expect((await lineupAgentRows(t)).map((row) => row.lineup)).toEqual([ + "kept", + ]); + // A row left behind for the tombstone (say by a write before this + // table existed) goes with the tombstone. + await t.run(async (ctx) => { + const gone = (await ctx.db.query("lineups").collect()).find( + (row) => row.publicId === "gone", + )!; + await ctx.db.insert("lineupAgents", { + strategyId: gone.strategyId, + pageId: gone.pageId, + lineupId: gone._id, + originId: "gone-origin", + agentType: "astra", + }); + }); + + vi.setSystemTime(Date.now() + 31 * 24 * 60 * 60 * 1000); + await t.mutation(purgeOldTombstones, {}); + await t.finishAllScheduledFunctions(vi.runAllTimers); + + const left = await t.run(async (ctx) => ({ + lineups: (await ctx.db.query("lineups").collect()).map( + (row) => row.publicId, + ), + lineupAgents: (await ctx.db.query("lineupAgents").collect()).length, + })); + expect(left).toEqual({ lineups: ["kept"], lineupAgents: 1 }); + expect((await lineupAgentRows(t)).map((row) => row.lineup)).toEqual([ + "kept", + ]); + }); + + test("a duplicate's lineups get agent rows of their own", async () => { + const { t, owner } = await createHarness(); + await apply(owner, "owner-client", [ + addOp(lineup("astra-a", { originId: "astra", agentType: "astra" }), 0), + addOp(lineup("astra-b", { originId: "astra", agentType: "astra" }), 1), + addOp(lineup("viper-a", { originId: "viper", agentType: "viper" }), 2), + ]); + const sourceRows = await lineupAgentRows(t); + + await owner.mutation(duplicateStrategy, { + clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, + sourceStrategyPublicId: strategyPublicId, + publicId: "lineups-copy", + name: "Lineups (Copy)", + }); + + const copy = (await owner.query(getFullSnapshot, { + clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, + strategyPublicId: "lineups-copy", + })) as { + pages: Array<{ publicId: string }>; + lineups: Array; + }; + const copyRows = await lineupAgentRows(t, "lineups-copy"); + expect(copyRows).toEqual( + copy.lineups + .map((row) => { + const origin = row.payload.data.origin as { + id: string; + agent: { type: string }; + }; + return { + lineup: row.publicId, + page: row.pagePublicId, + originId: origin.id, + agentType: origin.agent.type, + }; + }) + .sort((a, b) => a.lineup.localeCompare(b.lineup)), + ); + expect(copyRows).toHaveLength(3); + expect(copyRows.map((row) => row.page)).toEqual( + copyRows.map(() => copy.pages[0]!.publicId), + ); + // The source keeps its rows untouched. + expect(await lineupAgentRows(t)).toEqual(sourceRows); + expect(await agentSummary(t, "lineups-copy")).toEqual(["astra", "viper"]); + + // Editing the copy moves only the copy's rows. + const viperCopy = copyRows.find((row) => row.agentType === "viper")!; + const viperCopyLanding = copy.lineups.find( + (row) => row.publicId === viperCopy.lineup, + )!.payload.data.landing as { id: string }; + const edited = await apply( + owner, + "copy-client", + [ + { + opId: "copy-viper-to-jett", + type: "lineup.patch", + lineupPublicId: viperCopy.lineup, + pagePublicId: viperCopy.page, + payload: lineupPayload(viperCopy.lineup, { + originId: viperCopy.originId, + landingId: viperCopyLanding.id, + agentType: "jett", + }), + expectedLineupRevision: 1, + }, + ], + "lineups-copy", + ); + expect(edited[0]).toMatchObject({ status: "applied" }); + expect( + (await lineupAgentRows(t, "lineups-copy")).find( + (row) => row.lineup === viperCopy.lineup, + ), + ).toEqual({ ...viperCopy, agentType: "jett" }); + expect(await agentSummary(t, "lineups-copy")).toEqual(["astra", "jett"]); + expect(await lineupAgentRows(t)).toEqual(sourceRows); + expect(await agentSummary(t)).toEqual(["astra", "viper"]); + }); +}); + +describe("a resent lineup op", () => { + test("reports the revision it landed at, so a successor cannot overwrite a teammate's later edit", async () => { + const { owner, editor } = await createHarness(); + await apply(owner, "owner-client", [addOp(fanIn[0]!, 0)]); + const mine = patchOp( + "owner-renames", + lineup("from-heaven", { + originId: "heaven", + landingId: "site", + name: "Owner name", + }), + 1, + ); + const first = await apply(owner, "owner-client", [mine]); + expect(first[0]).toMatchObject({ status: "applied", appliedRevision: 2 }); + + // A teammate edits the same lineup after it. + const theirs = lineup("from-heaven", { + originId: "heaven", + landingId: "site", + name: "Editor name", + }); + const teammate = await apply(editor, "editor-client", [ + patchOp("editor-renames", theirs, 2), + ]); + expect(teammate[0]).toMatchObject({ status: "applied", appliedRevision: 3 }); + + // The owner's ack was lost: it sends the op again, with the successor it + // queued on top of it (expecting the revision the first landed at). + const resent = await apply(owner, "owner-client", [ + mine, + patchOp( + "owner-notes", + lineup("from-heaven", { + originId: "heaven", + landingId: "site", + name: "Owner name", + notes: "Owner notes", + }), + 2, + ), + ]); + expect(resent[0]).toEqual({ + opId: "owner-renames", + status: "noop", + currentRevision: 2, + }); + expect(resent[1]).toMatchObject({ + status: "rejected", + reason: "revision_mismatch", + current: { type: "lineup", revision: 3, value: theirs.payload }, + }); + const row = byKey(await pageLineups(owner)).get("from-heaven")!; + expect(row.revision).toBe(3); + expect(row.payload).toEqual(theirs.payload); + }); }); function elementPayload( diff --git a/convex/maintenance.ts b/convex/maintenance.ts index e1d4180f..f3306540 100644 --- a/convex/maintenance.ts +++ b/convex/maintenance.ts @@ -19,7 +19,10 @@ import { queueAssetReclaim, sweepDeletedImageAssetsRef, } from "./images"; -import { refreshStrategyAgentSummary } from "./lib/strategyAgentSummary"; +import { + refreshStrategyAgentSummary, + syncLineupAgent, +} from "./lib/strategyAgentSummary"; import { PAGE_TRASH_RETENTION_MS } from "./lib/entities"; const MAINTENANCE_BATCH_SIZE = 200; @@ -110,7 +113,12 @@ async function purgeContentRows( purgedAll = false; break; } - await ctx.db.delete("elementId" in row ? row.elementId : row.lineupId); + if ("lineupId" in row) { + await syncLineupAgent(ctx, row.lineupId, null); + await ctx.db.delete(row.lineupId); + } else { + await ctx.db.delete(row.elementId); + } } for (const [strategyId, assetPublicIds] of assetIdsByStrategy) { await queueAssetReclaim(ctx, strategyId, assetPublicIds); diff --git a/convex/mediaLimits.test.ts b/convex/mediaLimits.test.ts index d1d26771..a86aada9 100644 --- a/convex/mediaLimits.test.ts +++ b/convex/mediaLimits.test.ts @@ -12,7 +12,9 @@ import * as images from "./images"; import { processAssetReclaimBatch } from "./images"; import { markAssetReferencesReady } from "./lib/assetReferences"; import { CURRENT_CLOUD_PROTOCOL_VERSION } from "./lib/cloudProtocol"; +import { refreshStrategyAgentSummary } from "./lib/strategyAgentSummary"; import * as maintenance from "./maintenance"; +import * as ops from "./ops"; import schema from "./schema"; import * as strategies from "./strategies"; import { modules } from "./test.setup"; @@ -1188,6 +1190,85 @@ describe("duplicate stays within transaction limits", () => { expect(summary?.agentTypes).toEqual(["jett"]); }); + test("the agent summary never reads lineup rows, however large they are", async () => { + const { t, owner } = await createHarness(); + const { strategyId, pageId } = await ids(t); + // 25 lineups as large as the op size cap allows: more than the 16 MiB a + // transaction may read, were the summary to read them. + const agentTypes = ["sova", "kayo", "viper"]; + await t.run(async (ctx) => { + const now = Date.now(); + for (let index = 0; index < 25; index++) { + await insertLineup(ctx, { + publicId: `big-${index}`, + strategyId, + pageId, + payloadKind: "lineup", + payloadVersion: 1, + payload: lineupPayload(`big-${index}`, { + agentType: agentTypes[index % agentTypes.length], + notes: largeText, + }), + sortIndex: index, + revision: 1, + deleted: false, + createdAt: now, + updatedAt: now, + }); + } + }); + const lineupBytes = await t.run(async (ctx) => + (await ctx.db.query("lineups").collect()) + .map((row) => getConvexSize(row as any)) + .reduce((a, b) => a + b, 0), + ); + expect(lineupBytes).toBeGreaterThan(16 * 1024 * 1024); + + const refreshRead = await t.run(async (ctx) => { + const counted = countReads(ctx); + await refreshStrategyAgentSummary(counted.ctx, strategyId); + return counted.bytesRead(); + }); + expect(refreshRead).toBeLessThan(64 * 1024); + const summary = async () => + await t.run(async (ctx) => { + const row = await ctx.db + .query("strategyAgentSummaries") + .withIndex("by_strategyId", (q) => q.eq("strategyId", strategyId)) + .unique(); + return row?.agentTypes; + }); + // 9 sova origins, 8 kayo, 8 viper. + expect(await summary()).toEqual(["sova", "kayo", "viper"]); + + // A lineup edit, which refreshes the summary, reads the lineup it + // edits and not the others. + const batchRead = await owner.run(async (ctx) => { + const counted = countReads(ctx); + await (ops.applyBatch as any)._handler(counted.ctx, { + ...protocol, + strategyPublicId, + clientId: "big-editor", + ops: [ + { + opId: "big-0-to-jett", + type: "lineup.patch", + lineupPublicId: "big-0", + pagePublicId, + payload: lineupPayload("big-0", { + agentType: "jett", + notes: largeText, + }), + expectedLineupRevision: 1, + }, + ], + }); + return counted.bytesRead(); + }); + expect(batchRead).toBeLessThan(lineupBytes / 4); + expect(await summary()).toEqual(["kayo", "sova", "viper", "jett"]); + }); + async function seedLinkImages(t: RootHarness, count: number) { const { strategyId, pageId } = await ids(t); await t.run(async (ctx) => { diff --git a/convex/ops.ts b/convex/ops.ts index e358f084..f6b02808 100644 --- a/convex/ops.ts +++ b/convex/ops.ts @@ -2,7 +2,10 @@ import { mutation, type MutationCtx } from "./_generated/server"; import { ConvexError, v, type Infer } from "convex/values"; import type { Doc, Id } from "./_generated/dataModel"; import { assertCallerIsAccount, assertStrategyRole } from "./lib/auth"; -import { refreshStrategyAgentSummary } from "./lib/strategyAgentSummary"; +import { + refreshStrategyAgentSummary, + syncLineupAgent, +} from "./lib/strategyAgentSummary"; import { expectAssets, referencedAssetIds, @@ -341,9 +344,7 @@ function invalidLineupData(message: string) { /// Checks one lineup row before it is stored. A row is one whole lineup, /// keyed by its id, holding its origin and landing: every row can be drawn -/// on its own, whatever other rows exist. Each end carries its spot's -/// version, which decides the copy drawn when lineups sharing the spot -/// disagree (see lineUpGraphFromCloudRows on the client). +/// on its own, whatever other rows exist. function assertLineupPayload( payload: unknown, lineupPublicId: string, @@ -382,22 +383,19 @@ function assertLineupPayload( // The row must carry both ends hydration draws; a row without them would // load as nothing and read as a deletion nobody made. if (!hasEnd(data.origin, "agent") || !hasEnd(data.landing, "ability")) { - throw invalidLineupData("Lineup has no versioned origin or landing"); + throw invalidLineupData("Lineup has no origin or landing"); } return payload as LineupPayload; } -/// Whether [end] is an origin or landing: an id, the marker it places under -/// [markerKey], and its spot's version (an integer from 1). +/// Whether [end] is an origin or landing: an id and the marker it places +/// under [markerKey]. function hasEnd(end: unknown, markerKey: "agent" | "ability"): boolean { return ( isRecord(end) && typeof end.id === "string" && end.id.length > 0 && - isRecord(end[markerKey]) && - typeof end.version === "number" && - Number.isInteger(end.version) && - end.version >= 1 + isRecord(end[markerKey]) ); } @@ -1496,6 +1494,7 @@ async function reconcileExpectedAssets( await syncElementAssetReferences(ctx, row._id, row); } else { await syncLineupAssetReferences(ctx, row._id, row); + await syncLineupAgent(ctx, row._id, row); } const now = Date.now(); const assetsBefore = referencedAssetIds(rowBefore); @@ -1581,6 +1580,9 @@ export const applyBatch = mutation({ .first(); if (existingEvent !== null) { const latest = await getTargetSnapshot(ctx, strategy, op); + // An accepted op replays at the revision it landed at, not the + // row's latest: a successor rebased onto a teammate's later edit + // would overwrite that edit unseen. const replayResult: OperationResult = existingEvent.status === "failed" ? { @@ -1596,7 +1598,7 @@ export const applyBatch = mutation({ latestRevision: latest?.revision, latestPayload: latest?.payload, } - : noop(latest?.revision); + : noop(existingEvent.appliedRevision ?? latest?.revision); results.push(toPublicResult(op, replayResult)); continue; } diff --git a/convex/pageTrash.test.ts b/convex/pageTrash.test.ts index e8bf685a..66a94b56 100644 --- a/convex/pageTrash.test.ts +++ b/convex/pageTrash.test.ts @@ -322,6 +322,9 @@ async function rowsOnPage(t: RootHarness, pageId: Id<"pages">) { references: (await ctx.db.query("assetReferences").collect()).filter( (reference) => reference.pageId === pageId, ), + lineupAgents: (await ctx.db.query("lineupAgents").collect()).filter( + (row) => row.pageId === pageId, + ), })); } @@ -367,6 +370,11 @@ describe("page trash", () => { expect(kept.contents).toHaveLength(1); expect(kept.references.map((reference) => reference.assetPublicId).sort()) .toEqual(["b-image", "k-shot"]); + // The lineups' agent rows stay with them for a restore; the summary + // below leaves them out because their page is in the trash. + expect(kept.lineupAgents.map((row) => row.lineupId).sort()).toEqual( + kept.lineups.map((row) => row._id).sort(), + ); const shell = (await owner.query(getShell, { ...protocol, @@ -875,6 +883,7 @@ describe("page trash", () => { lineups: [], contents: [], references: [], + lineupAgents: [], }); expect(await rowsOnPage(t, liveA!._id)).toEqual(aRows); expect(await livePageIds(owner)).toEqual([pageA, pageC]); @@ -1059,6 +1068,7 @@ describe("page trash", () => { lineups: [], contents: [], references: [], + lineupAgents: [], }); }); }); diff --git a/convex/schema.ts b/convex/schema.ts index 1247d0a1..3e593f5c 100644 --- a/convex/schema.ts +++ b/convex/schema.ts @@ -108,6 +108,8 @@ export default defineSchema({ // One row per lineup, keyed by the lineup's id (payload.data.id). A key // is unique within its strategy, not across strategies: a strategy // copied on a device keeps its original's lineup ids. + // This shape replaced the origin/landing/link rows with no migration: + // production and dev held no lineup rows when it shipped (2026-10-01). payloadKind: lineupPayloadKindValidator, payloadVersion: v.number(), payload: lineupPayloadValidator, @@ -119,9 +121,20 @@ export default defineSchema({ }) .index("by_strategyId_and_publicId", ["strategyId", "publicId"]) .index("by_pageId", ["pageId"]) - // Live lineups of a strategy, read for its agent summary. - .index("by_strategyId_and_deleted", ["strategyId", "deleted"]) .index("by_deleted_and_updatedAt", ["deleted", "updatedAt"]), + // The agent each live lineup starts from, one small row per lineup, kept + // in step with every lineup write (see lib/strategyAgentSummary.ts). The + // strategy's agent summary reads these instead of the lineup rows, whose + // image lists can make reading them all exceed a transaction's limits. + lineupAgents: defineTable({ + strategyId: v.id("strategies"), + pageId: v.id("pages"), + lineupId: v.id("lineups"), + originId: v.string(), + agentType: v.string(), + }) + .index("by_strategyId", ["strategyId"]) + .index("by_lineupId", ["lineupId"]), // Which content rows show which images: one small row per (element or // lineup row, image id it shows), kept in step with every content write // (see lib/assetReferences.ts). Media cleanup checks an image's references diff --git a/convex/strategies.ts b/convex/strategies.ts index cae79d16..58215401 100644 --- a/convex/strategies.ts +++ b/convex/strategies.ts @@ -3,6 +3,7 @@ import { getConvexSize, v, type Value } from "convex/values"; import { agentTypesOf, deleteStrategyAgentSummary, + lineupAgentOf, refreshStrategyAgentSummary, storeStrategyAgentSummary, } from "./lib/strategyAgentSummary"; @@ -44,7 +45,6 @@ import { assetIdsOfRow, insertAssetReferences, type ReferencingElement, - type ReferencingLineup, } from "./lib/assetReferences"; import { purgeDeletedPageOrphansRef } from "./maintenance"; import { markDeletedStrategyImageAssetsRef } from "./images"; @@ -768,7 +768,10 @@ export const duplicate = mutation({ ); } const copiedElements: ReferencingElement[] = []; - const copiedLineups: ReferencingLineup[] = []; + const copiedLineupAgents: Pick< + Doc<"lineupAgents">, + "pageId" | "originId" | "agentType" + >[] = []; for (const element of sourceElements) { const pageId = pageIdMap.get(element.pageId); if (element.deleted || pageId === undefined) continue; @@ -825,7 +828,17 @@ export const duplicate = mutation({ budget.spend({ documents: assetIdsOfRow(copiedLineup).size }); const lineupId = await ctx.db.insert("lineups", copiedLineup); await insertAssetReferences(ctx, { lineupId }, copiedLineup); - copiedLineups.push(copiedLineup); + const agent = lineupAgentOf(copiedLineup.payload); + if (agent !== null) { + budget.spend({ documents: 1 }); + await ctx.db.insert("lineupAgents", { + strategyId, + pageId, + lineupId, + ...agent, + }); + copiedLineupAgents.push({ pageId, ...agent }); + } } for (const [targetAssetPublicId, sourceAssetPublicId] of @@ -852,7 +865,7 @@ export const duplicate = mutation({ await storeStrategyAgentSummary( ctx, strategyId, - agentTypesOf(copiedElements, copiedLineups), + agentTypesOf(copiedElements, copiedLineupAgents), ); return { ok: true } as const; }, diff --git a/convex/strategyDuplicate.test.ts b/convex/strategyDuplicate.test.ts index fb597220..7d2067f6 100644 --- a/convex/strategyDuplicate.test.ts +++ b/convex/strategyDuplicate.test.ts @@ -80,12 +80,10 @@ const seedLineupData = { origin: { id: "origin-1", agent: { type: "sova", lineUpID: "origin-1" }, - version: 1, }, landing: { id: "item-1", ability: { type: "shock_dart", lineUpID: "item-1" }, - version: 1, }, }; @@ -238,24 +236,6 @@ function mockR2Deletes() { return fetchMock; } -/// The copy of a shared landing clients draw, as lineUpGraphFromCloudRows -/// picks it: the highest spot version, ties to the greatest lineup id. -function drawnLanding( - rows: Array<{ payload: { data: Record } }>, - landingId: string, -): Record { - const carriers = rows - .map((row) => row.payload.data) - .filter((data) => data.landing.id === landingId); - if (carriers.length === 0) throw new Error(`No lineup lands at ${landingId}`); - return carriers.reduce((best, data) => - data.landing.version > best.landing.version || - (data.landing.version === best.landing.version && data.id > best.id) - ? data - : best, - ).landing; -} - function deletedKeys(fetchMock: ReturnType): string[] { return fetchMock.mock.calls .map((call) => new URL(String(call[0])).pathname) @@ -364,12 +344,10 @@ describe("strategies:duplicate", () => { origin: { id: origin.id, agent: { type: "sova", lineUpID: origin.id }, - version: 1, }, landing: { id, ability: { type: "shock_dart", lineUpID: id }, - version: 1, }, }); @@ -592,119 +570,141 @@ describe("strategies:duplicate", () => { ); }); - test("a shared spot's copies keep their versions, so the copy draws the spot where the original did", async () => { + test("a duplicate copies each lineup row exactly, even where rows sharing a spot disagree on it", async () => { const { owner } = await createHarness(); - const versioned = "versions-source"; - const versionedPage = "versions-source-page"; + const exact = "exact-source"; + const exactPage = "exact-source-page"; await owner.mutation(createStrategy, { ...protocol, - publicId: versioned, - name: "Versions", + publicId: exact, + name: "Exact", mapData: "split", - initialPagePublicId: versionedPage, + initialPagePublicId: exactPage, initialPageName: "Setup", initialPageIsAttack: true, }); - const atY = { dx: 300, dy: 300 }; - const atX = { dx: 100, dy: 100 }; - // Two lineups into one landing whose copies disagree on where it is. - // A holds the newer spot (version 5) on a row never edited since; - // B holds an older spot (version 3) on a row edited five times since, - // and has the greater lineup id. Drawing by row revision or by lineup - // id alone would pick X; by spot version the spot is at Y. - const lineupA = lineupPayload("lineup-a", { - originId: "origin-a", - landingId: "spot", - landingPosition: atY, - landingVersion: 5, - name: "A", - }); - const lineupB = (notes: string) => + // A and B land in one spot but their rows hold it in different places; + // A and C start from one origin, also held in different places. C's + // landing shares C's id. Each row is drawn from its own copy, so the + // duplicate must carry every row over as it is, whatever the new ids. + const rows = [ + lineupPayload("lineup-a", { + originId: "origin-a", + landingId: "spot", + originPosition: { dx: 10, dy: 10 }, + landingPosition: { dx: 300, dy: 300 }, + name: "A", + notes: "Jump throw", + }), lineupPayload("lineup-b", { originId: "origin-b", landingId: "spot", - landingPosition: atX, - landingVersion: 3, + agentType: "brimstone", + originPosition: { dx: 20, dy: 20 }, + landingPosition: { dx: 100, dy: 100 }, name: "B", - notes, - }); - const ops: Array> = [ - ...[lineupA, lineupB("")].map((payload, index) => ({ + youtubeLink: "https://youtu.be/b", + }), + lineupPayload("lineup-c", { + originId: "origin-a", + landingId: "lineup-c", + originPosition: { dx: 40, dy: 40 }, + landingPosition: { dx: 500, dy: 500 }, + name: "C", + }), + ]; + const added = (await owner.mutation(applyBatch, { + ...protocol, + strategyPublicId: exact, + clientId: "exact", + ops: rows.map((payload, index) => ({ opId: `add-${payload.data.id}`, type: "lineup.add", lineupPublicId: payload.data.id, - pagePublicId: versionedPage, + pagePublicId: exactPage, payload, sortIndex: index, })), - ]; - for (let revision = 1; revision <= 5; revision++) { - ops.push({ - opId: `edit-b-${revision}`, - type: "lineup.patch", - lineupPublicId: "lineup-b", - pagePublicId: versionedPage, - payload: lineupB(`edit ${revision}`), - expectedLineupRevision: revision, - }); - } - const added = (await owner.mutation(applyBatch, { - ...protocol, - strategyPublicId: versioned, - clientId: "versions", - ops, })) as { results: Array<{ status: string }> }; expect(added.results.map((result) => result.status)).toEqual( - ops.map(() => "applied"), + rows.map(() => "applied"), ); - type Row = { - publicId: string; - revision: number; - payload: { data: Record }; - }; - const lineupsOf = async (strategyPublicId: string) => - ( - (await owner.query(getFullSnapshot, { - ...protocol, - strategyPublicId, - })) as { lineups: Row[] } - ).lineups; - const original = await lineupsOf(versioned); - const byName = (rows: Row[], name: string) => - rows.find((row) => row.payload.data.name === name)!; - // The trap is set: B wins on revision and on id, A on version. - expect(byName(original, "A").revision).toBe(1); - expect(byName(original, "B").revision).toBe(6); - expect(drawnLanding(original, "spot").ability.position).toEqual(atY); - await owner.mutation(duplicateStrategy, { ...protocol, - sourceStrategyPublicId: versioned, - publicId: "versions-copy", - name: "Versions (Copy)", + sourceStrategyPublicId: exact, + publicId: "exact-copy", + name: "Exact (Copy)", }); - const copied = await lineupsOf("versions-copy"); - const copyA = byName(copied, "A").payload.data; - const copyB = byName(copied, "B").payload.data; - // One spot still, under a new id, each copy keeping its version and - // place. - expect(copyA.landing.id).toBe(copyB.landing.id); - expect(copyA.landing.id).not.toBe("spot"); - expect(copyA.landing).toMatchObject({ - version: 5, - ability: { position: atY }, - }); - expect(copyB.landing).toMatchObject({ - version: 3, - ability: { position: atX }, + type Row = { publicId: string; payload: { data: Record } }; + const copied = ( + (await owner.query(getFullSnapshot, { + ...protocol, + strategyPublicId: "exact-copy", + })) as { lineups: Row[] } + ).lineups; + expect(copied.map((row) => row.payload.data.name).sort()).toEqual([ + "A", + "B", + "C", + ]); + + // Pair each source row with its copy and read off the new ids. + const newIdOf = new Map(); + const remember = (sourceId: string, copyId: string) => { + // One source id always becomes one copy id. + expect(newIdOf.get(sourceId) ?? copyId).toBe(copyId); + newIdOf.set(sourceId, copyId); + }; + const pairs = rows.map((sourcePayload) => { + const copy = copied.find( + (row) => row.payload.data.name === sourcePayload.data.name, + )!; + const data = copy.payload.data; + expect(copy.publicId).toBe(data.id); + remember(sourcePayload.data.id, data.id); + remember(sourcePayload.data.origin.id, data.origin.id); + remember(sourcePayload.data.landing.id, data.landing.id); + return { sourcePayload, copy }; }); - expect(copyA.origin.version).toBe(1); - expect(copyB.origin.version).toBe(1); - // So the copy draws the spot where the original did. - expect(drawnLanding(copied, copyA.landing.id).ability.position).toEqual( - atY, + // Ids that were apart stay apart, and none is reused from the source. + const sourceIds = [...newIdOf.keys()].sort(); + const copyIds = [...newIdOf.values()]; + expect(sourceIds).toEqual( + ["lineup-a", "lineup-b", "lineup-c", "origin-a", "origin-b", "spot"], ); + expect(new Set(copyIds).size).toBe(copyIds.length); + for (const copyId of copyIds) expect(sourceIds).not.toContain(copyId); + + // Each copy is its source row under the new ids, each marker following + // its end, every place and detail as it was. + for (const { sourcePayload, copy } of pairs) { + const data = sourcePayload.data; + const originId = newIdOf.get(data.origin.id)!; + const landingId = newIdOf.get(data.landing.id)!; + expect(copy.payload).toEqual({ + ...sourcePayload, + data: { + ...data, + id: newIdOf.get(data.id)!, + origin: { + ...data.origin, + id: originId, + agent: { ...data.origin.agent, lineUpID: originId }, + }, + landing: { + ...data.landing, + id: landingId, + ability: { ...data.landing.ability, lineUpID: landingId }, + }, + }, + }); + } + // So the copy's shared spot disagrees exactly as the original's did. + const [copyA, copyB] = pairs.map((pair) => pair.copy.payload.data); + expect(copyA!.landing.id).toBe(copyB!.landing.id); + expect(copyA!.landing.ability.position).toEqual({ dx: 300, dy: 300 }); + expect(copyB!.landing.ability.position).toEqual({ dx: 100, dy: 100 }); }); test("an image whose tombstone is purged is reclaimed once nothing shows it", async () => { diff --git a/convex/syncBoundaries.test.ts b/convex/syncBoundaries.test.ts index 4047c4a6..f5baa9c7 100644 --- a/convex/syncBoundaries.test.ts +++ b/convex/syncBoundaries.test.ts @@ -1742,6 +1742,70 @@ describe("a write that changes nothing", () => { } }); +describe("a resent op whose ack was lost", () => { + test("reports the revision it landed at, so a successor cannot overwrite a teammate's later edit", async () => { + const { t, owner } = await createHarness(); + await createBaseStrategy(owner); + await applyOps(owner, "first-client", [ + { + opId: "add", + type: "element.add", + elementPublicId: "element-a", + pagePublicId: pageA, + payload: textPayload("base"), + sortIndex: 0, + }, + ]); + const patch = (opId: string, text: string, expectedElementRevision: number) => ({ + opId, + type: "element.patch", + elementPublicId: "element-a", + pagePublicId: pageA, + payload: textPayload(text), + expectedElementRevision, + }); + const mine = patch("mine", "mine", 1); + const first = await applyOps(owner, "first-client", [mine]); + expect(first.results[0]).toMatchObject({ + status: "applied", + appliedRevision: 2, + }); + + // A teammate's device edits the element after it. + const teammate = await applyOps(owner, "teammate-client", [ + patch("theirs", "theirs", 2), + ]); + expect(teammate.results[0]).toMatchObject({ + status: "applied", + appliedRevision: 3, + }); + + // The first client never heard back: it sends the op again with the + // same id, and the successor it queued on top of it. + const resent = await applyOps(owner, "first-client", [ + mine, + patch("mine-next", "mine again", 2), + ]); + expect(resent.results[0]).toEqual({ + opId: "mine", + status: "noop", + currentRevision: 2, + }); + expect(resent.results[1]).toMatchObject({ + opId: "mine-next", + status: "rejected", + reason: "revision_mismatch", + current: { type: "element", revision: 3, value: textPayload("theirs") }, + }); + const row = await t.run(async (ctx) => + (await ctx.db.query("elements").collect()).find( + (element) => element.publicId === "element-a", + ), + ); + expect(row).toMatchObject({ revision: 3, payload: textPayload("theirs") }); + }); +}); + describe("replay safety after operation event expiry", () => { test("identical add acknowledges and different add rejects", async () => { const { t, owner } = await createHarness(); diff --git a/convex/testContent.helpers.ts b/convex/testContent.helpers.ts index 8ea9d1cd..ffea8ff4 100644 --- a/convex/testContent.helpers.ts +++ b/convex/testContent.helpers.ts @@ -9,6 +9,7 @@ import { syncElementAssetReferences, syncLineupAssetReferences, } from "./lib/assetReferences"; +import { syncLineupAgent } from "./lib/strategyAgentSummary"; export async function insertElement( ctx: MutationCtx, @@ -25,6 +26,7 @@ export async function insertLineup( ): Promise> { const id = await ctx.db.insert("lineups", lineup); await syncLineupAssetReferences(ctx, id, lineup); + await syncLineupAgent(ctx, id, lineup); return id; } @@ -34,11 +36,6 @@ export type TestLineup = { agentType?: string; originPosition?: { dx: number; dy: number }; landingPosition?: { dx: number; dy: number }; - // Each end's spot version (default 1). Lineups sharing a spot draw it - // from the copy with the highest version, ties going to the greatest - // lineup id. - originVersion?: number; - landingVersion?: number; name?: string; youtubeLink?: string; notes?: string; @@ -47,9 +44,8 @@ export type TestLineup = { /// A lineup row's payload as a client writes it: the lineup's details and /// whole copies of its origin (the agent) and landing (the ability), each -/// marker's `lineUpID` naming its end, and each end its spot's version. -/// Lineups that share a spot pass the same originId or landingId. The row's -/// key is [id]. +/// marker's `lineUpID` naming its end. Lineups that share a spot pass the +/// same originId or landingId. The row's key is [id]. export function lineupPayload(id: string, lineup: TestLineup = {}) { const originId = lineup.originId ?? `${id}-origin`; const landingId = lineup.landingId ?? `${id}-landing`; @@ -70,7 +66,6 @@ export function lineupPayload(id: string, lineup: TestLineup = {}) { position: lineup.originPosition ?? { dx: 0, dy: 0 }, lineUpID: originId, }, - version: lineup.originVersion ?? 1, }, landing: { id: landingId, @@ -79,7 +74,6 @@ export function lineupPayload(id: string, lineup: TestLineup = {}) { position: lineup.landingPosition ?? { dx: 0, dy: 0 }, lineUpID: landingId, }, - version: lineup.landingVersion ?? 1, }, }, }; From 0c7099601065224a1e3c3b4ad117808b8256d75c Mon Sep 17 00:00:00 2001 From: Dara Adedeji Date: Fri, 2 Oct 2026 11:44:04 -0400 Subject: [PATCH 06/14] Draw disagreeing copies of a spot apart instead of healing them Shared spots were resolved by version and stale copies healed on page open, which meant writing rows the user never touched: a heal could replace recovered outbox work, and a rejected heal next to an accepted delete moved a spot backwards. Now copies of a spot that agree draw as one shared spot, as before. Copies that disagree, which only happens after a partial sync or a conflict, draw as separate spots: the value the row with the smallest lineup id carries keeps the real id, every other value gets a local alias. Every client draws the same thing and nothing is written; moving one of those spots patches only the rows carrying it, under the real id. Also: a change refused because a teammate deleted the item is settled, not kept, once the user deletes the item too, so Keep mine can't bring it back. Co-Authored-By: Claude Opus 5.5 (1M context) --- lib/collab/cloud_lineup_rows.dart | 292 +++--- .../active_page_live_sync_provider.dart | 82 +- .../collab/strategy_op_queue_provider.dart | 18 +- .../strategy_page_session_provider.dart | 14 - lib/strategy/strategy_page_source.dart | 19 +- test/cloud_lineup_rows_test.dart | 534 ++++++----- test/collab_sync_models_test.dart | 6 +- test/global_strategy_outbox_test.dart | 4 +- test/strategy_cloud_migration_test.dart | 10 +- test/strategy_op_queue_provider_test.dart | 222 ++++- test/strategy_page_session_provider_test.dart | 893 ++++++++++++------ .../runtime/lib/workload.dart | 2 - 12 files changed, 1362 insertions(+), 734 deletions(-) diff --git a/lib/collab/cloud_lineup_rows.dart b/lib/collab/cloud_lineup_rows.dart index d0140252..cf568b0a 100644 --- a/lib/collab/cloud_lineup_rows.dart +++ b/lib/collab/cloud_lineup_rows.dart @@ -8,15 +8,9 @@ import 'package:icarus/const/line_provider.dart'; /// images, and whole copies of its origin and landing. /// /// Lineups that share a spot (several lineups from one origin, or into one -/// landing) each carry their own copy of it, under the spot's id, with the -/// spot's version. The server never joins rows, so every row can be drawn on -/// its own. The client draws the copies of one id as one spot again -/// ([lineUpGraphFromCloudRows]) and writes every row of a spot with the copy -/// it draws ([cloudLineupRows]), so copies that drifted apart (a change that -/// reached only some of the rows) come back together. -/// -/// Versions live only here, in the cloud rows: the canvas, Hive and .ica -/// files never see them. +/// landing) each carry their own copy of it, under the spot's id. The server +/// never joins rows, so every row can be drawn on its own, and a lineup's +/// row only ever changes when the user changes that lineup or its spot. class CloudLineupRow { const CloudLineupRow({required this.publicId, required this.payload}); @@ -28,141 +22,121 @@ class CloudLineupRow { final CloudPayload payload; } -/// A page's lineups as cloud rows describe them, with the version of each -/// spot drawn. +/// Local ids standing for real origin and landing ids, alias to real id. +/// They exist only on the canvas (see [lineUpGraphFromCloudRows]); rows are +/// always written under the real id. +class CloudLineupAliases { + const CloudLineupAliases({this.origins = const {}, this.landings = const {}}); + + static const none = CloudLineupAliases(); + + final Map origins; + final Map landings; + + bool get isEmpty => origins.isEmpty && landings.isEmpty; + + CloudLineupAliases followedBy(CloudLineupAliases other) { + if (other.isEmpty) return this; + return CloudLineupAliases( + origins: {...origins, ...other.origins}, + landings: {...landings, ...other.landings}, + ); + } +} + +/// A page's lineups as cloud rows describe them, and the aliases drawing +/// them took. class CloudLineups { const CloudLineups({ required this.graph, - this.originVersions = const {}, - this.landingVersions = const {}, + this.aliases = CloudLineupAliases.none, }); - static const empty = CloudLineups(graph: LineUpGraph.empty); - final LineUpGraph graph; + final CloudLineupAliases aliases; +} - /// The version of each origin and landing drawn, by id. - final Map originVersions; - final Map landingVersions; +/// A page's lineups as its live [rows] describe them. +/// +/// Links come from the rows, in their order. The copies the rows carry of +/// one origin (or landing) id are grouped by value. Copies that agree, as +/// they do unless a change reached only some of a spot's lineups, are drawn +/// as one spot under the real id. Copies that disagree are drawn apart, one +/// spot per value: the value the row with the smallest lineup id carries +/// keeps the real id, and every other value is drawn under the alias +/// `@`. Every client draws the same +/// spots from the same rows, and nothing is written for it: each lineup +/// keeps its own copy until the user changes it. Spots appear in the order +/// the rows first carry them. +/// +/// Throws a [FormatException] naming the row when one cannot be read. +CloudLineups lineUpGraphFromCloudRows(Iterable rows) { + final read = [for (final row in rows) _readLineupRow(row)]; + final originIds = _spotIds([ + for (final (link, origin, _) in read) (link.id, origin.id, origin.toJson()), + ]); + final landingIds = _spotIds([ + for (final (link, _, landing) in read) + (link.id, landing.id, landing.toJson()), + ]); + + final origins = {}; + final landings = {}; + final links = []; + for (final (link, origin, landing) in read) { + final originId = originIds.byLineup[link.id]!; + final landingId = landingIds.byLineup[link.id]!; + origins.putIfAbsent(originId, () => _originAs(origin, originId)); + landings.putIfAbsent(landingId, () => _landingAs(landing, landingId)); + links.add(link.copyWith(originId: originId, landingId: landingId)); + } + return CloudLineups( + graph: LineUpGraph( + origins: origins.values.toList(), + landings: landings.values.toList(), + links: links, + ), + aliases: CloudLineupAliases( + origins: originIds.aliases, + landings: landingIds.aliases, + ), + ); } /// The rows that store [graph]: one per link, in link order, each carrying -/// its origin and landing as they are now. A link whose origin or landing is +/// its origin and landing as they are now, under their real ids ([aliases] +/// maps the canvas's aliases back). A link whose origin or landing is /// missing draws nothing on the canvas, so it has no row either. -/// -/// [drawn] is what the canvas was drawn from. A spot keeps the version it -/// was drawn at while it is unchanged, and goes one past it once changed, -/// so the change outranks every copy the rows hold. A spot [drawn] lacks -/// starts at version 1. List cloudLineupRows( LineUpGraph graph, { - CloudLineups drawn = CloudLineups.empty, + CloudLineupAliases aliases = CloudLineupAliases.none, }) { - final drawnOrigins = { - for (final origin in drawn.graph.origins) origin.id: origin, - }; - final drawnLandings = { - for (final landing in drawn.graph.landings) landing.id: landing, - }; - int versionOf( - String id, - Map now, - Map? before, - Map versions, - ) { - final version = versions[id]; - if (version == null || before == null) return 1; - return _sameJson(now, before) ? version : version + 1; - } - - final origins = {for (final origin in graph.origins) origin.id: origin}; - final landings = {for (final landing in graph.landings) landing.id: landing}; - final originJson = >{ + final origins = { for (final origin in graph.origins) - origin.id: { - ...origin.toJson(), - 'version': versionOf( - origin.id, - origin.toJson(), - drawnOrigins[origin.id]?.toJson(), - drawn.originVersions, - ), - }, + origin.id: _originAs(origin, aliases.origins[origin.id] ?? origin.id), }; - final landingJson = >{ + final landings = { for (final landing in graph.landings) - landing.id: { - ...landing.toJson(), - 'version': versionOf( - landing.id, - landing.toJson(), - drawnLandings[landing.id]?.toJson(), - drawn.landingVersions, - ), - }, + landing.id: + _landingAs(landing, aliases.landings[landing.id] ?? landing.id), }; return [ for (final link in graph.links) - if (origins.containsKey(link.originId) && - landings.containsKey(link.landingId)) + if ((origins[link.originId], landings[link.landingId]) + case (final origin?, final landing?)) CloudLineupRow( publicId: link.id, payload: cloudLineupPayload({ ...link.toJson() ..remove('originId') ..remove('landingId'), - 'origin': originJson[link.originId], - 'landing': landingJson[link.landingId], + 'origin': origin.toJson(), + 'landing': landing.toJson(), }), ), ]; } -/// A page's lineups as its live [rows] describe them. -/// -/// Links come from the rows, in their order. Origins and landings are the -/// rows' copies, one per id, in the order the rows first carry them. When -/// copies of a spot disagree, every client draws the one with the highest -/// version, a tie going to the row with the greatest lineup id. -/// -/// Throws a [FormatException] naming the row when one cannot be read. -CloudLineups lineUpGraphFromCloudRows(Iterable rows) { - final origins = {}; - final landings = {}; - final links = []; - bool outranks(int version, String rowId, (Object, int, String)? drawn) => - drawn == null || - version > drawn.$2 || - (version == drawn.$2 && rowId.compareTo(drawn.$3) > 0); - - for (final row in rows) { - final (link, origin, originVersion, landing, landingVersion) = - _readLineupRow(row); - links.add(link); - if (outranks(originVersion, row.publicId, origins[origin.id])) { - origins[origin.id] = (origin, originVersion, row.publicId); - } - if (outranks(landingVersion, row.publicId, landings[landing.id])) { - landings[landing.id] = (landing, landingVersion, row.publicId); - } - } - return CloudLineups( - graph: LineUpGraph( - origins: [for (final (origin, _, _) in origins.values) origin], - landings: [for (final (landing, _, _) in landings.values) landing], - links: links, - ), - originVersions: { - for (final MapEntry(:key, value: (_, version, _)) in origins.entries) - key: version, - }, - landingVersions: { - for (final MapEntry(:key, value: (_, version, _)) in landings.entries) - key: version, - }, - ); -} - /// A page's lineups as its [lineups] on the server describe them. Deleted /// rows draw nothing. LineUpGraph lineUpGraphFromRemoteLineups(Iterable lineups) { @@ -184,19 +158,78 @@ bool isRetiredCloudLineupOp(StrategyOp op) { final _retiredLineupKey = RegExp(r'^lineup(Origin|Landing|Link):'); -(LineUpLink, LineUpOrigin, int, LineUpLanding, int) _readLineupRow( - CloudLineupRow row, +/// The spot id each lineup's copy of one kind of end is drawn under, and the +/// aliases among them; see [lineUpGraphFromCloudRows]. +({Map byLineup, Map aliases}) _spotIds( + List<(String lineupId, String endId, Map value)> copies, ) { + // End id -> value -> the lineups carrying that value. + final groups = >>{}; + for (final (lineupId, endId, value) in copies) { + ((groups[endId] ??= {})[_valueKey(value)] ??= []).add(lineupId); + } + final byLineup = {}; + final aliases = {}; + String smallest(List ids) => ids.reduce( + (a, b) => a.compareTo(b) <= 0 ? a : b, + ); + for (final MapEntry(key: endId, value: byValue) in groups.entries) { + final firsts = { + for (final MapEntry(:key, value: lineups) in byValue.entries) + key: smallest(lineups), + }; + final realValue = firsts.entries + .reduce((a, b) => a.value.compareTo(b.value) <= 0 ? a : b) + .key; + for (final MapEntry(key: value, value: lineups) in byValue.entries) { + final id = value == realValue ? endId : '$endId@${firsts[value]}'; + if (id != endId) aliases[id] = endId; + for (final lineupId in lineups) { + byLineup[lineupId] = id; + } + } + } + return (byLineup: byLineup, aliases: aliases); +} + +String _valueKey(Map value) => + canonicalCloudJsonEncode(jsonDecode(jsonEncode(value))); + +/// [origin] under [id], its agent's `lineUpID` following when it named the +/// origin's old id. +LineUpOrigin _originAs(LineUpOrigin origin, String id) { + if (id == origin.id) return origin; + final agent = origin.agent; + return LineUpOrigin( + id: id, + agent: agent.lineUpID == origin.id + ? (agent.copyWith(lineUpID: id)..isDeleted = agent.isDeleted) + : agent, + ); +} + +/// [landing] under [id], its ability's `lineUpID` following when it named +/// the landing's old id. +LineUpLanding _landingAs(LineUpLanding landing, String id) { + if (id == landing.id) return landing; + final ability = landing.ability; + return LineUpLanding( + id: id, + ability: ability.lineUpID == landing.id + ? (ability.copyWith(lineUpID: id)..isDeleted = ability.isDeleted) + : ability, + ); +} + +(LineUpLink, LineUpOrigin, LineUpLanding) _readLineupRow(CloudLineupRow row) { try { final kind = row.payload['kind']; if (kind != cloudLineupPayloadKind) { throw FormatException('unknown lineup kind $kind'); } final data = cloudPayloadData(row.payload); - final originJson = _object(data['origin'], 'origin'); - final landingJson = _object(data['landing'], 'landing'); - final origin = LineUpOrigin.fromJson(originJson); - final landing = LineUpLanding.fromJson(landingJson); + final origin = LineUpOrigin.fromJson(_object(data['origin'], 'origin')); + final landing = LineUpLanding.fromJson(_object(data['landing'], 'landing')); final link = LineUpLink.fromJson({ ...data, 'originId': origin.id, @@ -205,13 +238,7 @@ final _retiredLineupKey = RegExp(r'^lineup(Origin|Landing|Link):'); if (link.id != row.publicId) { throw FormatException('it holds lineup ${link.id}'); } - return ( - link, - origin, - _version(originJson, 'origin'), - landing, - _version(landingJson, 'landing'), - ); + return (link, origin, landing); } catch (error, stackTrace) { Error.throwWithStackTrace( FormatException('Cloud lineup ${row.publicId} could not be read: ' @@ -225,16 +252,3 @@ Map _object(Object? value, String name) { if (value is! Map) throw FormatException('it has no $name'); return Map.from(value); } - -int _version(Map end, String name) { - final version = end['version']; - if (version is! num || version != version.roundToDouble() || version < 1) { - throw FormatException('its $name has no version'); - } - return version.toInt(); -} - -bool _sameJson(Map left, Map right) { - return canonicalCloudJsonEncode(jsonDecode(jsonEncode(left))) == - canonicalCloudJsonEncode(jsonDecode(jsonEncode(right))); -} diff --git a/lib/providers/collab/active_page_live_sync_provider.dart b/lib/providers/collab/active_page_live_sync_provider.dart index 6c899813..0c2a21ee 100644 --- a/lib/providers/collab/active_page_live_sync_provider.dart +++ b/lib/providers/collab/active_page_live_sync_provider.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'dart:developer'; import 'dart:math' show max; @@ -14,6 +15,7 @@ import 'package:icarus/providers/ability_provider.dart'; import 'package:icarus/providers/agent_provider.dart'; import 'package:icarus/providers/collab/active_page_live_sync_models.dart'; import 'package:icarus/providers/collab/remote_strategy_snapshot_provider.dart'; +import 'package:icarus/providers/collab/strategy_conflict_provider.dart'; import 'package:icarus/providers/collab/strategy_op_queue_provider.dart'; import 'package:icarus/providers/drawing_provider.dart'; import 'package:icarus/providers/image_provider.dart'; @@ -87,6 +89,17 @@ class ActivePageLiveSyncNotifier extends Notifier { /// hold ends, but no longer ordered against anything on the server. final Set _heldDeletedKeys = {}; + /// Every alias drawing lineups has given a spot (see + /// lineUpGraphFromCloudRows), so rows are written under the real ids. An + /// alias names its real id and the lineup it came from, so one alias + /// always means the same spot and they are only ever added. + CloudLineupAliases _lineupAliases = CloudLineupAliases.none; + + /// Records the aliases a canvas about to be drawn from cloud rows uses. + void noteLineupAliases(CloudLineupAliases aliases) { + _lineupAliases = _lineupAliases.followedBy(aliases); + } + @override ActivePageLiveSyncState build() { return const ActivePageLiveSyncState(); @@ -516,6 +529,7 @@ class ActivePageLiveSyncNotifier extends Notifier { state.overlayByEntityKey, ); final retainedDesiredOps = {}; + final settledAttention = {}; for (final key in pageKeys) { if (_remoteAdoptionPending.contains(key)) { @@ -550,6 +564,16 @@ class ActivePageLiveSyncNotifier extends Notifier { continue; } + // The user deleted, on the canvas, an item whose change the server + // refused: the server has it deleted too, so the refused change has + // nothing left to keep, and Keep mine must not bring the item back. + if (local == null && + existingOverlay != null && + (remote == null || remote.deleted) && + queueState.attentionByEntityKey.containsKey(key)) { + settledAttention.add(key); + } + if (matchesHydratedBase && !shouldPreserveTouched) { if (nextOverlay.remove(key) != null) { _debugLog('overlay.remove $key reason=unchanged_since_hydration'); @@ -634,6 +658,20 @@ class ActivePageLiveSyncNotifier extends Notifier { ); } + if (settledAttention.isNotEmpty) { + // A refusal still waiting to be announced is moot too, as when the + // user chooses Use cloud. + for (final key in settledAttention) { + final refused = queueState.attentionByEntityKey[key]!.pending.op.opId; + ref.read(strategyConflictProvider.notifier).clear(refused); + } + unawaited( + ref + .read(strategyOpQueueProvider.notifier) + .settleAttention(settledAttention), + ); + } + final desiredOpsByEntityKey = { ...retainedDesiredOps, }; @@ -961,14 +999,14 @@ class ActivePageLiveSyncNotifier extends Notifier { ); } - // One row per lineup, carrying its origin and landing as the canvas draws - // them. A change to a shared spot therefore changes every lineup on it, - // and a row whose stored copy of a spot is not the drawn one is written - // with the drawn one: compared with the rows as stored, it differs. + // One row per lineup, carrying its origin and landing as the canvas + // draws them, under their real ids. A change to a shared spot changes + // every lineup drawn on it; a lineup nobody changed matches its stored + // row, so it is never written. final freshLineupSortIndex = freshSortIndexes(EntitySyncKeyKind.lineup); for (final row in cloudLineupRows( ref.read(lineUpProvider).graph, - drawn: _drawnLineups(pageId), + aliases: _lineupAliases, )) { final key = EntitySyncKey.lineup(pageId, row.publicId); entities[key] = _NormalizedEntity( @@ -984,40 +1022,6 @@ class ActivePageLiveSyncNotifier extends Notifier { return entities; } - /// Whether a lineup row of [pageId] holds a copy of a spot other than the - /// one the canvas draws (a change reached only some rows of the spot, or a - /// conflict was settled for one row), or other work the canvas holds for - /// it. The next sync writes each such row as the canvas has it. - bool lineupsNeedHealing(String pageId) { - if (state.hydratedPageId != pageId) return false; - return _normalizedLocalEntities(pageId).entries.any( - (entry) => - entry.key.kind == EntitySyncKeyKind.lineup && - !_entitiesEquivalent( - entry.value, - _hydratedBaseByEntityKey[entry.key], - ), - ); - } - - /// [pageId]'s lineups as the canvas was drawn from them: the live lineup - /// rows it was hydrated from, as accepted since. Their spot versions are - /// what a change on the canvas builds on. - CloudLineups _drawnLineups(String pageId) { - return lineUpGraphFromCloudRows([ - for (final MapEntry(:key, value: base) - in _hydratedBaseByEntityKey.entries) - if (key.pageId == pageId && - key.kind == EntitySyncKeyKind.lineup && - key.entityId != null && - !base.deleted) - CloudLineupRow( - publicId: key.entityId!, - payload: Map.from(base.payload as Map), - ), - ]); - } - List<_CollabElementEnvelope> _collectLocalElementEnvelopes() { final envelopes = <_CollabElementEnvelope>[]; diff --git a/lib/providers/collab/strategy_op_queue_provider.dart b/lib/providers/collab/strategy_op_queue_provider.dart index 1908e0d1..57a81186 100644 --- a/lib/providers/collab/strategy_op_queue_provider.dart +++ b/lib/providers/collab/strategy_op_queue_provider.dart @@ -1125,7 +1125,21 @@ class StrategyOpQueueNotifier extends Notifier { /// changing unrelated queued, in-flight, paused, or rejected work. Future> discardRejected( Set entityKeys, - ) { + ) => + _dropAttention(entityKeys, adoptRemote: true); + + /// Drops the refused work of entities the user has since deleted on the + /// canvas while the server has them deleted too. Both sides agree they + /// are gone, so nothing is left to keep, and Keep mine must not bring + /// them back. Unlike [discardRejected] the canvas is already the truth: + /// there is nothing to adopt. + Future> settleAttention(Set entityKeys) => + _dropAttention(entityKeys, adoptRemote: false); + + Future> _dropAttention( + Set entityKeys, { + required bool adoptRemote, + }) { return _serializeWrite(() async { final attention = Map.from( state.attentionByEntityKey, @@ -1168,7 +1182,7 @@ class StrategyOpQueueNotifier extends Notifier { _uncertainOversizedParking.remove(storageKey); attention.remove(key); successors.remove(key); - _awaitingRemoteAdoption.add(key); + if (adoptRemote) _awaitingRemoteAdoption.add(key); discarded.add(key); } catch (error, stackTrace) { persistenceError = error; diff --git a/lib/providers/strategy_page_session_provider.dart b/lib/providers/strategy_page_session_provider.dart index 455b8d4b..084108c1 100644 --- a/lib/providers/strategy_page_session_provider.dart +++ b/lib/providers/strategy_page_session_provider.dart @@ -840,7 +840,6 @@ class StrategyPageSessionNotifier extends Notifier { snapshot: loadedRemoteSnapshot, ); _lastAppliedRemoteSnapshot = loadedRemoteSnapshot; - _healLineupsOf(pageData.pageId); } _updateHydrationBookkeeping( pageData.pageId, @@ -1309,7 +1308,6 @@ class StrategyPageSessionNotifier extends Notifier { keepBaseFor: heldBack, ); _lastAppliedRemoteSnapshot = snapshot; - _healLineupsOf(pageId); _updateHydrationBookkeeping( pageId, hydrationKey: _buildRemotePageHydrationKey(snapshot, pageId), @@ -1317,18 +1315,6 @@ class StrategyPageSessionNotifier extends Notifier { if (heldBack.isNotEmpty) _remoteChangeWaitsForEditor = true; } - /// Sends the drawn copy of each shared spot to the lineup rows of - /// [pageId] that hold another, as soon as the page is drawn: until then a - /// lineup whose row still holds the old copy would show it to anyone who - /// opens the page after the lineup drawing the new copy is deleted. - void _healLineupsOf(String pageId) { - if (ref.read(activePageLiveSyncProvider.notifier).lineupsNeedHealing( - pageId, - )) { - unawaited(ref.read(strategyProvider.notifier).notifyCloudMutation()); - } - } - String? _resolveHydrationTargetPage(RemoteEditorSnapshot snapshot) { if (snapshot.pages.isEmpty) { return null; diff --git a/lib/strategy/strategy_page_source.dart b/lib/strategy/strategy_page_source.dart index 2567f620..949626cf 100644 --- a/lib/strategy/strategy_page_source.dart +++ b/lib/strategy/strategy_page_source.dart @@ -280,7 +280,13 @@ class CloudStrategyPageSource implements StrategyPageSource { } } - final lineUpGraph = lineUpGraphFromRemoteLineups(lineups); + final cloudLineups = lineUpGraphFromCloudRows([ + for (final lineup in lineups) + if (!lineup.deleted) CloudLineupRow.remote(lineup), + ]); + ref + .read(activePageLiveSyncProvider.notifier) + .noteLineupAliases(cloudLineups.aliases); final mapValue = Maps.mapNames.entries.firstWhere( (entry) => entry.value == snapshot.header.mapData, @@ -309,7 +315,7 @@ class CloudStrategyPageSource implements StrategyPageSource { texts: texts, images: images, utilities: utilities, - lineUpGraph: lineUpGraph, + lineUpGraph: cloudLineups.graph, ); } @@ -449,10 +455,13 @@ class CloudStrategyPageSource implements StrategyPageSource { } } - final lineUpGraph = lineUpGraphFromCloudRows([ + final cloudLineups = lineUpGraphFromCloudRows([ for (final lineup in projected.lineups) CloudLineupRow(publicId: lineup.publicId, payload: lineup.payload), - ]).graph; + ]); + ref + .read(activePageLiveSyncProvider.notifier) + .noteLineupAliases(cloudLineups.aliases); final mapValue = Maps.mapNames.entries.firstWhere( (entry) => entry.value == snapshot.header.mapData, @@ -473,7 +482,7 @@ class CloudStrategyPageSource implements StrategyPageSource { texts: texts, images: images, utilities: utilities, - lineUpGraph: lineUpGraph, + lineUpGraph: cloudLineups.graph, ); } diff --git a/test/cloud_lineup_rows_test.dart b/test/cloud_lineup_rows_test.dart index 5dd5ad50..82167e0c 100644 --- a/test/cloud_lineup_rows_test.dart +++ b/test/cloud_lineup_rows_test.dart @@ -8,14 +8,18 @@ import 'package:icarus/const/agents.dart'; import 'package:icarus/const/line_provider.dart'; import 'package:icarus/const/placed_classes.dart'; -LineUpOrigin _origin(String id, {Offset position = const Offset(10, 20)}) => +LineUpOrigin _origin( + String id, { + Offset position = const Offset(10, 20), + String? lineUpID, +}) => LineUpOrigin( id: id, agent: PlacedAgent( id: 'agent-$id', type: AgentType.sova, position: position, - lineUpID: id, + lineUpID: lineUpID ?? id, ), ); @@ -30,7 +34,8 @@ LineUpLanding _landing(String id, {Offset position = const Offset(30, 40)}) => ), ); -/// Two origins into one landing, and a second lineup out of origin a. +/// Two origins into one landing (fan-in), and a second lineup out of +/// origin a (fan-out). LineUpGraph _shared() => LineUpGraph( origins: [_origin('a'), _origin('b')], landings: [_landing('shared'), _landing('other')], @@ -48,67 +53,65 @@ LineUpGraph _shared() => LineUpGraph( ], ); +/// The row of lineup [id] from [origin] into [landing], as a client that +/// saw that copy of each wrote it. +CloudLineupRow _row(String id, LineUpOrigin origin, LineUpLanding landing) => + cloudLineupRows(LineUpGraph( + origins: [origin], + landings: [landing], + links: [ + LineUpLink(id: id, originId: origin.id, landingId: landing.id), + ], + )).single; + +const _here = Offset(30, 40); +const _moved = Offset(99, 99); +const _elsewhere = Offset(55, 55); + +/// Lineups k1, k2 and k3 into landing `shared`: k1 carries it [_here], k2 +/// and k3 a copy [_moved] that never reached k1. +List _splitLanding() => [ + _row('k1', _origin('o1'), _landing('shared')), + _row('k2', _origin('o2'), _landing('shared', position: _moved)), + _row('k3', _origin('o3'), _landing('shared', position: _moved)), + ]; + String _json(LineUpGraph graph) => jsonEncode(graph.toJson()); +/// [value] as plain JSON. +Object? _plain(Object? value) => jsonDecode(jsonEncode(value)); + /// [rows] as canonical JSON, to compare them exactly. String _canonical(Iterable rows) => - canonicalCloudJsonEncode(jsonDecode(jsonEncode([ + canonicalCloudJsonEncode(_plain([ for (final row in rows) {'publicId': row.publicId, 'payload': row.payload}, - ]))); - -/// [json], as JSON, with a version, as a row carries a spot. -Map _versioned(Object json, int version) => { - ...jsonDecode(jsonEncode(json)) as Map, - 'version': version, - }; + ])); /// The [end] (`origin` or `landing`) [row] carries. Map _end(CloudLineupRow row, String end) => Map.from((row.payload['data'] as Map)[end] as Map); -/// [row] with its ends' versions set to [origin] and [landing] (or removed, -/// for [_noVersion]), as another client may have written it. -CloudLineupRow _atVersions( - CloudLineupRow row, { - Object? origin, - Object? landing, -}) { - Map withVersion(String end, Object? version) { - final json = _end(row, end); - if (version == null) return json; - if (identical(version, _noVersion)) return json..remove('version'); - return json..['version'] = version; - } - - return CloudLineupRow( - publicId: row.publicId, - payload: { - ...row.payload, - 'data': { - ...Map.from(row.payload['data'] as Map), - 'origin': withVersion('origin', origin), - 'landing': withVersion('landing', landing), - }, - }, - ); -} - -const _noVersion = Object(); +/// Each lineup's origin and landing ids as [read] draws them. +Map _ends(CloudLineups read) => { + for (final link in read.graph.links) + link.id: (link.originId, link.landingId), + }; -/// [_shared] with landing `shared` moved. -LineUpGraph _sharedMoved() => LineUpGraph( - origins: _shared().origins, - landings: [ - _landing('shared', position: const Offset(99, 99)), - _landing('other'), - ], - links: _shared().links, - ); +Offset _originAt(CloudLineups read, String id) => + read.graph.origins.firstWhere((o) => o.id == id).agent.position; Offset _landingAt(CloudLineups read, String id) => read.graph.landings.firstWhere((l) => l.id == id).ability.position; +/// Every order of [items]. +List> _orders(List items) => items.length <= 1 + ? [items] + : [ + for (final (i, first) in items.indexed) + for (final rest in _orders([...items]..removeAt(i))) [first, ...rest], + ]; + void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -129,226 +132,289 @@ void main() { 'origin', 'landing', }); - // A spot nothing was drawn from yet starts at version 1. - expect(data['origin'], _versioned(_origin('a').toJson(), 1)); - expect(data['landing'], _versioned(_landing('shared').toJson(), 1)); + // An end is the spot itself, nothing added. + expect(_plain(data['origin']), _plain(_origin('a').toJson())); + expect(_plain(data['landing']), _plain(_landing('shared').toJson())); expect(data['images'], [ {'id': 'img-1', 'fileExtension': '.png'}, ]); }); - test('shared spots read back as one spot, sharing intact', () { - final read = lineUpGraphFromCloudRows(cloudLineupRows(_shared())); + test('copies that agree draw one shared spot under its real id', () { + final rows = cloudLineupRows(_shared()); + + for (final order in _orders(rows)) { + final read = lineUpGraphFromCloudRows(order); + expect(read.aliases.isEmpty, isTrue); + expect(read.graph.origins.map((o) => o.id).toSet(), {'a', 'b'}); + expect(read.graph.landings.map((l) => l.id).toSet(), {'shared', 'other'}); + expect(_ends(read), { + 'k1': ('a', 'shared'), + 'k2': ('b', 'shared'), + 'k3': ('a', 'other'), + }); + } + // Fan-in and fan-out round-trip: the page drawn is the page written, + // and writing it again gives back the same rows. + final read = lineUpGraphFromCloudRows(rows); expect(_json(read.graph), _json(_shared())); - expect(read.originVersions, {'a': 1, 'b': 1}); - expect(read.landingVersions, {'shared': 1, 'other': 1}); + final written = cloudLineupRows(read.graph, aliases: read.aliases); + expect(_canonical(written), _canonical(rows)); }); - test('fan-in and fan-out round-trip through rows drawn and written again', + test('copies that disagree draw apart, the smallest lineup id keeping the id', () { - // Rows another client left at versions past 1. + for (final order in _orders(_splitLanding())) { + final read = lineUpGraphFromCloudRows(order); + + expect(_ends(read), { + 'k1': ('o1', 'shared'), + 'k2': ('o2', 'shared@k2'), + 'k3': ('o3', 'shared@k2'), + }); + expect(_landingAt(read, 'shared'), _here); + expect(_landingAt(read, 'shared@k2'), _moved); + expect(read.graph.landings, hasLength(2)); + expect(read.aliases.landings, {'shared@k2': 'shared'}); + expect(read.aliases.origins, isEmpty); + } + + // Which copy keeps the id follows the lineup ids, not which copy is + // newer: here the moved copy is k1's. + final read = lineUpGraphFromCloudRows([ + _row('k2', _origin('o2'), _landing('shared')), + _row('k1', _origin('o1'), _landing('shared', position: _moved)), + ]); + expect(_ends(read), { + 'k2': ('o2', 'shared@k2'), + 'k1': ('o1', 'shared'), + }); + expect(_landingAt(read, 'shared'), _moved); + expect(_landingAt(read, 'shared@k2'), _here); + }); + + test('spots appear in the order the rows first carry them', () { + final read = lineUpGraphFromCloudRows(_splitLanding().reversed); + + expect(read.graph.origins.map((o) => o.id), ['o3', 'o2', 'o1']); + expect(read.graph.landings.map((l) => l.id), ['shared@k2', 'shared']); + expect(read.graph.links.map((l) => l.id), ['k3', 'k2', 'k1']); + }); + + test('three copies that disagree draw three spots', () { final rows = [ - for (final row in cloudLineupRows(_shared())) - _atVersions(row, origin: 2, landing: 4), + _row('k1', _origin('o1'), _landing('shared')), + _row('k2', _origin('o2'), _landing('shared', position: _moved)), + _row('k3', _origin('o3'), _landing('shared', position: _elsewhere)), + _row('k4', _origin('o4'), _landing('shared', position: _moved)), ]; - final read = lineUpGraphFromCloudRows(rows); - expect(_json(read.graph), _json(_shared())); - expect(read.originVersions, {'a': 2, 'b': 2}); - expect(read.landingVersions, {'shared': 4, 'other': 4}); + for (final order in _orders(rows)) { + final read = lineUpGraphFromCloudRows(order); + + expect(_ends(read), { + 'k1': ('o1', 'shared'), + 'k2': ('o2', 'shared@k2'), + 'k3': ('o3', 'shared@k3'), + 'k4': ('o4', 'shared@k2'), + }); + expect(_landingAt(read, 'shared'), _here); + expect(_landingAt(read, 'shared@k2'), _moved); + expect(_landingAt(read, 'shared@k3'), _elsewhere); + expect(read.aliases.landings, { + 'shared@k2': 'shared', + 'shared@k3': 'shared', + }); + } + }); - // Nothing changed, so writing what was drawn gives back the same rows, - // versions and all. - final written = cloudLineupRows(read.graph, drawn: read); - expect(_canonical(written), _canonical(rows)); - expect(_json(lineUpGraphFromCloudRows(written).graph), _json(_shared())); + test('an origin and a landing split on their own', () { + const far = Offset(70, 80); + final read = lineUpGraphFromCloudRows([ + _row('k1', _origin('a'), _landing('l')), + // Only the origin disagrees. + _row('k2', _origin('a', position: far), _landing('l')), + // Only the landing disagrees. + _row('k3', _origin('a'), _landing('l', position: _moved)), + ]); + + expect(_ends(read), { + 'k1': ('a', 'l'), + 'k2': ('a@k2', 'l'), + 'k3': ('a', 'l@k3'), + }); + expect(_originAt(read, 'a'), const Offset(10, 20)); + expect(_originAt(read, 'a@k2'), far); + expect(_landingAt(read, 'l'), _here); + expect(_landingAt(read, 'l@k3'), _moved); + expect(read.aliases.origins, {'a@k2': 'a'}); + expect(read.aliases.landings, {'l@k3': 'l'}); }); - test('a link missing its origin or landing has no row', () { - final rows = cloudLineupRows(LineUpGraph( - origins: [_origin('a')], - landings: [_landing('l')], - links: [ - LineUpLink(id: 'k1', originId: 'a', landingId: 'l'), - LineUpLink(id: 'dangling', originId: 'a', landingId: 'missing'), + test('an unchanged page writes back every row exactly', () { + final pages = { + 'agreeing': cloudLineupRows(_shared()), + 'split landing': _splitLanding(), + 'split origin and landing': [ + _row('k1', _origin('a'), _landing('l')), + _row('k2', _origin('a', position: _elsewhere), _landing('l')), + _row('k3', _origin('a'), _landing('l', position: _moved)), + _row('k4', _origin('a', position: _elsewhere), _landing('l')), ], - )); + }; - expect(rows.map((row) => row.publicId), ['k1']); + for (final MapEntry(key: page, value: rows) in pages.entries) { + // Rows as the server returns them. + final stored = [ + for (final row in rows) + CloudLineupRow( + publicId: row.publicId, + payload: _plain(row.payload) as Map, + ), + ]; + final hydrated = lineUpGraphFromCloudRows(stored); + + final written = cloudLineupRows( + hydrated.graph, + aliases: hydrated.aliases, + ); + + expect(_canonical(written), _canonical(stored), reason: page); + } }); - test('a spot keeps its version while unchanged and goes one past it changed', - () { - final drawn = lineUpGraphFromCloudRows([ - for (final row in cloudLineupRows(_shared())) - _atVersions(row, origin: 3, landing: 5), - ]); - // Landing `shared` moved, lineup k1 renamed, and a new lineup from a new - // origin into a new landing. - final edited = LineUpGraph( - origins: [..._sharedMoved().origins, _origin('c')], - landings: [..._sharedMoved().landings, _landing('fresh')], - links: [ - for (final link in _sharedMoved().links) - link.id == 'k1' - ? LineUpLink( - id: 'k1', - originId: 'a', - landingId: 'shared', - name: 'A, renamed', - ) - : link, - LineUpLink(id: 'k4', originId: 'c', landingId: 'fresh'), - ], - ); + test('moving a spot drawn apart changes only the rows carrying it', () { + final rows = _splitLanding(); + final hydrated = lineUpGraphFromCloudRows(rows); + LineUpGraph moving(String id, Offset to) => LineUpGraph( + origins: hydrated.graph.origins, + landings: [ + for (final landing in hydrated.graph.landings) + landing.id == id + ? LineUpLanding( + id: id, + ability: landing.ability.copyWith(position: to), + ) + : landing, + ], + links: hydrated.graph.links, + ); - final rows = cloudLineupRows(edited, drawn: drawn); + // The aliased spot: k2 and k3 change, under the real id. + final aliasMoved = cloudLineupRows( + moving('shared@k2', _elsewhere), + aliases: hydrated.aliases, + ); + expect(_canonical([aliasMoved[0]]), _canonical([rows[0]])); + for (final row in aliasMoved.skip(1)) { + final landing = _end(row, 'landing'); + expect(landing['id'], 'shared'); + expect((landing['ability'] as Map)['lineUpID'], 'shared'); + expect( + _plain(landing), + _plain(_landing('shared', position: _elsewhere).toJson()), + ); + } + expect(_end(aliasMoved[1], 'landing'), _end(aliasMoved[2], 'landing')); + // The spot that kept the id: only k1 changes. + final realMoved = cloudLineupRows( + moving('shared', _elsewhere), + aliases: hydrated.aliases, + ); expect( - { - for (final row in rows) - row.publicId: ( - _end(row, 'origin')['version'], - _end(row, 'landing')['version'], - ), - }, - { - // A lineup's own fields are not its spots: renaming k1 leaves - // origin a at 3. Every row carrying the moved landing carries it - // one past the version drawn. - 'k1': (3, 6), - 'k2': (3, 6), - 'k3': (3, 5), - // Spots nothing was drawn from start at 1. - 'k4': (1, 1), - }, + _plain(_end(realMoved[0], 'landing')), + _plain(_landing('shared', position: _elsewhere).toJson()), ); - expect(_end(rows[1], 'landing'), _end(rows[0], 'landing')); + expect(_canonical(realMoved.skip(1)), _canonical(rows.skip(1))); }); - test('a change that reached only some rows outranks the copies it missed', - () { - final before = [ - for (final row in cloudLineupRows(_shared())) - _atVersions(row, origin: 1, landing: 5), + test("a marker's lineUpID follows its spot's alias, and back", () { + final rows = [ + _row('k1', _origin('a'), _landing('l')), + _row('k2', _origin('a', position: _moved), + _landing('l', position: _moved)), + // A marker that never named its spot keeps what it named. + _row( + 'k3', + _origin('a', position: _elsewhere, lineUpID: 'unrelated'), + _landing('l'), + ), ]; - final drawn = lineUpGraphFromCloudRows(before); - final moved = cloudLineupRows(_sharedMoved(), drawn: drawn); - expect(_end(moved[0], 'landing')['version'], 6); - - // Only k1's write landed. k2 still holds the old copy and, at an equal - // version, would win on its greater lineup id. - final partial = lineUpGraphFromCloudRows([moved[0], before[1], before[2]]); - - expect(_landingAt(partial, 'shared'), const Offset(99, 99)); - expect(partial.landingVersions['shared'], 6); - - // Writing what is drawn brings k2's copy back in line, at the same - // version, since the drawn spot itself did not change. - final healed = cloudLineupRows(partial.graph, drawn: partial); - expect(_end(healed[1], 'landing'), _end(moved[0], 'landing')); - expect(_canonical(healed), _canonical(moved)); - }); + final hydrated = lineUpGraphFromCloudRows(rows); - test('disagreeing copies draw the highest version, then the greatest id', () { - final original = cloudLineupRows(_shared()); - final moved = cloudLineupRows(_sharedMoved()); - // Every order the rows can arrive in draws the same spot. - void expectDrawn( - List rows, - Offset position, - int version, - ) { - for (final order in [rows, rows.reversed.toList()]) { - final read = lineUpGraphFromCloudRows(order); - expect(_landingAt(read, 'shared'), position); - expect(read.landingVersions['shared'], version); - } - } - - // k2 carries the moved copy at a higher version than k1. - expectDrawn( - [ - _atVersions(original[0], landing: 3), - _atVersions(moved[1], landing: 4), - ], - const Offset(99, 99), - 4, + final origins = { + for (final origin in hydrated.graph.origins) + origin.id: origin.agent.lineUpID, + }; + expect(origins, {'a': 'a', 'a@k2': 'a@k2', 'a@k3': 'unrelated'}); + expect( + hydrated.graph.landings + .firstWhere((l) => l.id == 'l@k2') + .ability + .lineUpID, + 'l@k2', ); - // The original copy ahead wins though its lineup id is lower. - expectDrawn( - [ - _atVersions(original[0], landing: 3), - _atVersions(moved[1], landing: 2), - ], - const Offset(30, 40), - 3, + + final written = cloudLineupRows(hydrated.graph, aliases: hydrated.aliases); + expect((_end(written[1], 'origin')['agent'] as Map)['lineUpID'], 'a'); + expect((_end(written[1], 'landing')['ability'] as Map)['lineUpID'], 'l'); + expect( + (_end(written[2], 'origin')['agent'] as Map)['lineUpID'], + 'unrelated', ); - // A tie goes to the greatest lineup id (k2 over k1). - expectDrawn( - [ - _atVersions(original[0], landing: 5), - _atVersions(moved[1], landing: 5), + expect(_canonical(written), _canonical(rows)); + }); + + test('a lineup added to a spot drawn apart is written under the real id', () { + final rows = _splitLanding(); + final hydrated = lineUpGraphFromCloudRows(rows); + final added = LineUpGraph( + origins: [...hydrated.graph.origins, _origin('o4')], + landings: hydrated.graph.landings, + links: [ + ...hydrated.graph.links, + LineUpLink(id: 'k4', originId: 'o4', landingId: 'shared@k2'), ], - const Offset(99, 99), - 5, ); - // Each spot is ranked on its own: origin a is drawn from k3, landing - // shared from k1. - final movedOrigin = cloudLineupRows(LineUpGraph( - origins: [_origin('a', position: const Offset(77, 77)), _origin('b')], - landings: _shared().landings, - links: _shared().links, - )); - final read = lineUpGraphFromCloudRows([ - _atVersions(moved[0], origin: 1, landing: 2), - _atVersions(original[1], origin: 1, landing: 1), - _atVersions(movedOrigin[2], origin: 2, landing: 1), - ]); + final written = cloudLineupRows(added, aliases: hydrated.aliases); + + expect(_canonical(written.take(3)), _canonical(rows)); expect( - read.graph.origins.firstWhere((o) => o.id == 'a').agent.position, - const Offset(77, 77), + _plain(_end(written[3], 'landing')), _plain(_end(rows[1], 'landing'))); + expect(_end(written[3], 'landing')['id'], 'shared'); + // And it is drawn again on the spot it was added to. + expect(_ends(lineUpGraphFromCloudRows(written))['k4'], ('o4', 'shared@k2')); + }); + + test('aliases followed by more aliases keep both', () { + const first = CloudLineupAliases( + origins: {'a@k2': 'a'}, + landings: {'l@k3': 'l'}, ); - expect(_landingAt(read, 'shared'), const Offset(99, 99)); - expect(read.originVersions, {'a': 2, 'b': 1}); - expect(read.landingVersions, {'shared': 2, 'other': 1}); - // A spot keeps the place its first row gave it, whichever row's copy is - // drawn. - expect(read.graph.origins.map((o) => o.id), ['a', 'b']); - expect(read.graph.landings.map((l) => l.id), ['shared', 'other']); + const second = CloudLineupAliases(origins: {'b@k5': 'b'}); + + final both = first.followedBy(second); + + expect(both.origins, {'a@k2': 'a', 'b@k5': 'b'}); + expect(both.landings, {'l@k3': 'l'}); + expect(identical(first.followedBy(CloudLineupAliases.none), first), isTrue); + expect(CloudLineupAliases.none.isEmpty, isTrue); }); - test('an end without a whole positive version fails loudly, naming the row', - () { - final valid = cloudLineupRows(_shared()).first; - for (final end in ['origin', 'landing']) { - for (final version in [_noVersion, 0, -1, 1.5, '2', true]) { - final row = end == 'origin' - ? _atVersions(valid, origin: version) - : _atVersions(valid, landing: version); - expect( - () => lineUpGraphFromCloudRows([row]), - throwsA(isA().having( - (error) => error.message, - 'message', - allOf( - contains('Cloud lineup k1 could not be read'), - contains('its $end has no version'), - ), - )), - reason: '$end at $version', - ); - } - } - // A version the server returns as a float64 integer reads as that - // integer. - final read = lineUpGraphFromCloudRows([ - _atVersions(valid, origin: 2.0, landing: 3.0), - ]); - expect(read.originVersions, {'a': 2}); - expect(read.landingVersions, {'shared': 3}); + test('a link missing its origin or landing has no row', () { + final rows = cloudLineupRows(LineUpGraph( + origins: [_origin('a')], + landings: [_landing('l')], + links: [ + LineUpLink(id: 'k1', originId: 'a', landingId: 'l'), + LineUpLink(id: 'dangling', originId: 'a', landingId: 'missing'), + ], + )); + + expect(rows.map((row) => row.publicId), ['k1']); }); test('a row of any other shape fails loudly, naming the row', () { @@ -384,7 +450,11 @@ void main() { () => lineUpGraphFromCloudRows([ CloudLineupRow(publicId: 'someone-else', payload: valid), ]), - throwsA(isA()), + throwsA(isA().having( + (error) => error.message, + 'message', + contains('Cloud lineup someone-else could not be read'), + )), reason: 'a row keyed apart from its lineup', ); }); diff --git a/test/collab_sync_models_test.dart b/test/collab_sync_models_test.dart index 1f1aa73d..62b46964 100644 --- a/test/collab_sync_models_test.dart +++ b/test/collab_sync_models_test.dart @@ -408,8 +408,6 @@ void main() { 'position': {'dx': 10, 'dy': 20}, 'lineUpID': 'origin-1', }, - // Convex returns every number as a float64. - 'version': 1.0, }, 'landing': { 'id': 'landing-1', @@ -419,7 +417,6 @@ void main() { 'position': {'dx': 30, 'dy': 40}, 'lineUpID': 'landing-1', }, - 'version': 2.0, }, }, }; @@ -435,8 +432,7 @@ void main() { expect(landing.id, 'landing-1'); expect(landing.ability.lineUpID, 'landing-1'); expect(landing.ability.position, const Offset(30, 40)); - expect(read.originVersions, {'origin-1': 1}); - expect(read.landingVersions, {'landing-1': 2}); + expect(read.aliases.isEmpty, isTrue); }); test('ability info accepts Convex float64 integers and rejects fractions', diff --git a/test/global_strategy_outbox_test.dart b/test/global_strategy_outbox_test.dart index 461054ba..c0558107 100644 --- a/test/global_strategy_outbox_test.dart +++ b/test/global_strategy_outbox_test.dart @@ -251,8 +251,8 @@ void main() { pagePublicId: 'page-one', payload: cloudLineupPayload({ 'id': 'k', - 'origin': {'id': 'o', 'agent': {}, 'version': 1}, - 'landing': {'id': 'l', 'ability': {}, 'version': 1}, + 'origin': {'id': 'o', 'agent': {}}, + 'landing': {'id': 'l', 'ability': {}}, }), sortIndex: 0, ); diff --git a/test/strategy_cloud_migration_test.dart b/test/strategy_cloud_migration_test.dart index 8fb5e594..7fe2172b 100644 --- a/test/strategy_cloud_migration_test.dart +++ b/test/strategy_cloud_migration_test.dart @@ -78,7 +78,7 @@ List _upload( /// server returns them. LineUpGraph _hydrate(List adds) => _hydrated(adds).graph; -/// [_hydrate], with the version of each spot drawn. +/// [_hydrate], with the aliases drawing it took. CloudLineups _hydrated(List adds) { return lineUpGraphFromCloudRows([ for (final add in adds) @@ -104,9 +104,6 @@ void main() { final data = cloudPayloadData(adds.single.payload); expect((data['origin'] as Map)['id'], 'origin-1'); expect((data['landing'] as Map)['id'], 'link-1'); - // An upload is the spots' first cloud copy. - expect((data['origin'] as Map)['version'], 1); - expect((data['landing'] as Map)['version'], 1); // Hydrating the upload gives back the page's graph, ids and all, so the // page never authors a patch for a lineup nobody touched. expect( @@ -132,7 +129,10 @@ void main() { // And the rows reproduce exactly when the hydrated graph is sent again. final hydrated = _hydrated(adds); - final resent = cloudLineupRows(hydrated.graph, drawn: hydrated); + final resent = cloudLineupRows( + hydrated.graph, + aliases: hydrated.aliases, + ); expect( _canonical([for (final row in resent) row.payload]), _canonical([for (final add in adds) add.payload]), diff --git a/test/strategy_op_queue_provider_test.dart b/test/strategy_op_queue_provider_test.dart index 4f6d33ac..e2ab301b 100644 --- a/test/strategy_op_queue_provider_test.dart +++ b/test/strategy_op_queue_provider_test.dart @@ -2143,6 +2143,83 @@ void main() { expect(store.values, isEmpty); }); + test('a replayed predecessor rebases its successor where it was applied', + () async { + final store = MemoryDurableStrategyOutboxStore(); + final repository = _SequencedAckRepository(); + final container = _cloudQueueContainer( + store: store, + repository: repository, + ); + addTearDown(container.dispose); + final notifier = container.read(strategyOpQueueProvider.notifier) + ..setActiveStrategy('strategy-1', accountId: 'account-a'); + const key = EntitySyncKey.element('page-1', 'element-1'); + + await notifier.enqueue(_elementPatch( + opId: 'first-edit', + value: 'first', + expectedRevision: 1, + )); + final firstFlush = notifier.flushNow(); + await repository.firstStarted.future; + await notifier.syncDesiredOpsForPage( + pageId: 'page-1', + desiredOpsByEntityKey: { + key: _elementPatch( + opId: 'second-edit', + value: 'second', + expectedRevision: 1, + ), + }, + ); + + // The server applied first-edit at 2, its ack was lost, and a + // teammate's edit took the row to 3 before first-edit was sent again. + // The replay is answered as a noop at the revision first-edit was + // applied at, not the row's revision now. + repository.completeFirst(const NoopOpAck( + opId: 'first-edit', + currentRevision: 2, + )); + await firstFlush; + await repository.secondStarted.future; + + final promoted = repository.calls[1].single as ElementPatchOp; + expect(promoted.payload, {'value': 'second'}); + expect(promoted.expectedElementRevision, 2); + + // So the server, its row at 3, refuses it rather than letting it + // overwrite the teammate's edit unseen. + repository.completeSecond( + promoted.expectedElementRevision == 3 + ? AppliedOpAck(opId: promoted.opId, revision: 4) + : RejectedOpAck( + opId: promoted.opId, + rejectionReason: OpRejectionReason.revisionMismatch, + current: const ElementCurrentSnapshot( + revision: 3, + value: {'value': 'teammate'}, + ), + ), + ); + await repository.secondCompleted.future; + await _settle(); + + final current = container.read(strategyOpQueueProvider); + expect(current.queuedByEntityKey, isEmpty); + expect(current.successorByEntityKey, isEmpty); + expect( + current.attentionByEntityKey[key]!.pending.op.payload, + {'value': 'second'}, + ); + final record = store.load().records.single; + expect(record.status, DurableOutboxStatus.attention); + expect(record.pending.op.opId, promoted.opId); + expect(record.lastError, OpRejectionReason.revisionMismatch.wireName); + expect(record.latestServerRevision, 3); + }); + test('restart replays an element predecessor before its successor', () async { final store = MemoryDurableStrategyOutboxStore(); @@ -3042,6 +3119,147 @@ void main() { expect(record.lastError, OpRejectionReason.deleted.wireName); } }); + + test( + 'work deleted on both sides is settled, and keep mine cannot revive it', + () async { + final store = MemoryDurableStrategyOutboxStore(); + final repository = _TombstoneRepository(); + const elementPatch = ElementPatchOp( + opId: 'move-element', + elementPublicId: 'element-1', + pagePublicId: 'page-1', + payload: {'value': 'moved'}, + sortIndex: 4, + expectedElementRevision: 1, + ); + final lineupPatch = LineupPatchOp( + opId: 'move-lineup', + lineupPublicId: 'k', + pagePublicId: 'page-1', + payload: _lineupAdd(opId: 'unused', name: 'moved').payload, + sortIndex: 3, + expectedLineupRevision: 1, + ); + // Refused alongside them, and not deleted on the canvas. + const unrelatedPatch = ElementPatchOp( + opId: 'move-other', + elementPublicId: 'element-2', + pagePublicId: 'page-1', + payload: {'value': 'other'}, + sortIndex: 5, + expectedElementRevision: 1, + ); + final elementKey = EntitySyncKey.forStrategyOp(elementPatch)!; + final lineupKey = EntitySyncKey.forStrategyOp(lineupPatch)!; + final unrelatedKey = EntitySyncKey.forStrategyOp(unrelatedPatch)!; + final (container, notifier) = await refused( + repository, + store, + [elementPatch, lineupPatch, unrelatedPatch], + ); + + // The user deleted element-1 and lineup k on the canvas. + final settled = await notifier.settleAttention({elementKey, lineupKey}); + + expect(settled, {elementKey, lineupKey}); + expect( + container.read(strategyOpQueueProvider).attentionByEntityKey.keys, + [unrelatedKey], + ); + expect( + [for (final record in store.load().records) record.entityKey], + [unrelatedKey], + ); + + // Keep mine brings back only the work still in attention. + await notifier.retryRejected(flushImmediately: false); + expect( + container.read(strategyOpQueueProvider).queuedByEntityKey.keys, + [unrelatedKey], + ); + await notifier.flushNow(); + await _settle(); + expect(repository.restored.keys, ['element-2']); + expect(repository.calls, hasLength(2)); + expect( + [for (final op in repository.calls.last) op.entityPublicId], + ['element-2'], + ); + + // Settling adopts nothing from the server: a later edit to a settled + // entity is queued and sent as any other. + const restore = ElementAddOp( + opId: 'add-again', + elementPublicId: 'element-1', + pagePublicId: 'page-1', + payload: {'value': 'again'}, + sortIndex: 4, + expectedElementRevision: 2, + ); + await notifier.syncDesiredOpsForPage( + pageId: 'page-1', + desiredOpsByEntityKey: {elementKey: restore}, + clearMissing: false, + ); + expect( + container + .read(strategyOpQueueProvider) + .queuedByEntityKey[elementKey]! + .pending + .op + .payload, + {'value': 'again'}, + ); + await notifier.flushNow(); + await _settle(); + expect(repository.restored.keys, ['element-2', 'element-1']); + final current = container.read(strategyOpQueueProvider); + expect(current.attentionByEntityKey, isEmpty); + expect(current.queuedByEntityKey, isEmpty); + expect(store.load().records, isEmpty); + }); + + test('discarding instead waits for the server copy to be adopted', + () async { + // The contrast that makes settling distinct: after Use cloud the + // canvas is about to be redrawn from the server, so desired work for + // the entity is ignored until adoption completes. + final store = MemoryDurableStrategyOutboxStore(); + final repository = _TombstoneRepository(); + const elementPatch = ElementPatchOp( + opId: 'move-element', + elementPublicId: 'element-1', + pagePublicId: 'page-1', + payload: {'value': 'moved'}, + sortIndex: 4, + expectedElementRevision: 1, + ); + final key = EntitySyncKey.forStrategyOp(elementPatch)!; + final (container, notifier) = + await refused(repository, store, [elementPatch]); + + await notifier.discardRejected({key}); + await notifier.syncDesiredOpsForPage( + pageId: 'page-1', + desiredOpsByEntityKey: { + key: const ElementAddOp( + opId: 'add-again', + elementPublicId: 'element-1', + pagePublicId: 'page-1', + payload: {'value': 'again'}, + sortIndex: 4, + expectedElementRevision: 2, + ), + }, + clearMissing: false, + ); + + expect( + container.read(strategyOpQueueProvider).queuedByEntityKey, + isEmpty, + ); + }); }); group('a server that needs a newer Icarus', () { @@ -3283,8 +3501,8 @@ LineupAddOp _lineupAdd({ payload: cloudLineupPayload({ 'id': id, 'name': name, - 'origin': {'id': 'o', 'agent': {}, 'version': 1}, - 'landing': {'id': 'l', 'ability': {}, 'version': 1}, + 'origin': {'id': 'o', 'agent': {}}, + 'landing': {'id': 'l', 'ability': {}}, }), sortIndex: 0, ); diff --git a/test/strategy_page_session_provider_test.dart b/test/strategy_page_session_provider_test.dart index ff6fbb98..78dd8e40 100644 --- a/test/strategy_page_session_provider_test.dart +++ b/test/strategy_page_session_provider_test.dart @@ -515,6 +515,13 @@ class _ServerRepository implements ConvexStrategyRepository { final void Function() afterBatch; final List> batches = []; + /// While set, a batch waits for it before it reaches the server. + Completer? hold; + + /// While set, the next batch reaches the server, then this runs and the + /// answer is lost on its way back: the send fails as if offline. + void Function()? loseNextAnswer; + @override Future> applyBatch({ required String strategyPublicId, @@ -523,7 +530,13 @@ class _ServerRepository implements ConvexStrategyRepository { String? accountSubject, }) async { batches.add(ops); + await hold?.future; final acks = server.applyBatch(ops); + if (loseNextAnswer case final lose?) { + loseNextAnswer = null; + lose(); + throw const SocketException('The answer was lost.'); + } afterBatch(); return acks; } @@ -631,21 +644,19 @@ Map _abilityJson( 'armLengthsMeters': [10, 10, 10, 10], }; -/// Origin [id] as a lineup row carries it, at its spot's [version]. +/// Origin [id] as a lineup row carries it. Map _originJson( String id, [ Offset position = const Offset(10, 20), - int version = 1, ]) => - {'id': id, 'agent': _agentJson(id, position), 'version': version}; + {'id': id, 'agent': _agentJson(id, position)}; -/// Landing [id] as a lineup row carries it, at its spot's [version]. +/// Landing [id] as a lineup row carries it. Map _landingJson( String id, [ Offset position = const Offset(30, 40), - int version = 1, ]) => - {'id': id, 'ability': _abilityJson(id, position), 'version': version}; + {'id': id, 'ability': _abilityJson(id, position)}; /// One lineup as the server stores it: one row, the link [id] with its own /// copies of its [origin] and [landing]. @@ -731,7 +742,9 @@ RemoteLineup _editedLineup( /// A write expecting another revision than the row's is refused with the /// row as it is. A patch or reorder of a tombstone is refused as `deleted`; /// an add expecting the tombstone's revision brings the row back. A write -/// that would change nothing is a noop, whatever revision it expected. +/// that would change nothing is a noop, whatever revision it expected. An op +/// sent again (its answer lost) is answered as a noop at the revision it +/// landed at, or refused again. class _FakeServer { _FakeServer( this.pageId, { @@ -750,6 +763,9 @@ class _FakeServer { final Map _lineups = {}; final Map _elements = {}; + /// The answer to each element and lineup op taken, by op id. + final Map _answers = {}; + /// Every lineup op this server answered, in order. final List received = []; @@ -880,30 +896,48 @@ class _FakeServer { OpAck noop() => NoopOpAck(opId: op.opId, currentRevision: existing?.revision); - switch (change.kind) { - case StrategyOpKind.add: - final payload = change.payload!; - final sortIndex = change.sortIndex!; - if (existing == null) return store(payload, sortIndex); - if (existing.deleted) return stale() ?? store(payload, sortIndex); - return _sameJson(existing.payload, payload) - ? noop() - : refused(OpRejectionReason.alreadyExists); - case StrategyOpKind.delete: - if (existing == null || existing.deleted) return noop(); - return stale() ?? - store(existing.payload, existing.sortIndex, deleted: true); - case StrategyOpKind.patch || StrategyOpKind.reorder: - if (existing == null) return refused(OpRejectionReason.notFound); - if (existing.deleted) return refused(OpRejectionReason.deleted); - final payload = change.payload ?? existing.payload; - final sortIndex = change.sortIndex ?? existing.sortIndex; - if (_sameJson(payload, existing.payload) && - sortIndex == existing.sortIndex) { - return noop(); - } - return stale() ?? store(payload, sortIndex); + // An op already answered is answered again as it was: an accepted one + // as a noop at the revision it landed at, not the row's latest. + switch (_answers[op.opId]) { + case RejectedOpAck(:final rejectionReason): + return refused(rejectionReason); + case final OpAck accepted?: + return NoopOpAck( + opId: op.opId, + currentRevision: accepted.appliedRevision, + ); + case null: + break; + } + + OpAck answer() { + switch (change.kind) { + case StrategyOpKind.add: + final payload = change.payload!; + final sortIndex = change.sortIndex!; + if (existing == null) return store(payload, sortIndex); + if (existing.deleted) return stale() ?? store(payload, sortIndex); + return _sameJson(existing.payload, payload) + ? noop() + : refused(OpRejectionReason.alreadyExists); + case StrategyOpKind.delete: + if (existing == null || existing.deleted) return noop(); + return stale() ?? + store(existing.payload, existing.sortIndex, deleted: true); + case StrategyOpKind.patch || StrategyOpKind.reorder: + if (existing == null) return refused(OpRejectionReason.notFound); + if (existing.deleted) return refused(OpRejectionReason.deleted); + final payload = change.payload ?? existing.payload; + final sortIndex = change.sortIndex ?? existing.sortIndex; + if (_sameJson(payload, existing.payload) && + sortIndex == existing.sortIndex) { + return noop(); + } + return stale() ?? store(payload, sortIndex); + } } + + return _answers[op.opId] = answer(); } /// A teammate's change to lineup row [id] lands: [edit] changes its data, @@ -989,21 +1023,27 @@ class _Row { ); } -/// Moves the version of [end] (a lineup row's origin or landing) on by one, -/// as a client changing that spot does. -void _bumpVersion(Map end) => end['version'] = (end['version'] as int) + 1; - -/// The origin and landing ids and versions row [row] carries. -({String origin, int originVersion, String landing, int landingVersion}) - _endsOf(RemoteLineup row) { +/// The origin and landing row [row] carries: their ids, and where their +/// markers stand. +({String origin, Offset originAt, String landing, Offset landingAt}) _endsOf( + RemoteLineup row, +) { final data = cloudPayloadData(row.payload); final origin = data['origin'] as Map; final landing = data['landing'] as Map; + Offset at(Object? marker) { + final position = (marker! as Map)['position'] as Map; + return Offset( + (position['dx'] as num).toDouble(), + (position['dy'] as num).toDouble(), + ); + } + return ( origin: origin['id'] as String, - originVersion: origin['version'] as int, + originAt: at(origin['agent']), landing: landing['id'] as String, - landingVersion: landing['version'] as int, + landingAt: at(landing['ability']), ); } @@ -4759,45 +4799,56 @@ void main() { }); test( - 'copies of a shared spot that disagree draw the highest version, a tie ' - 'the greatest lineup id', () async { + 'copies of a shared spot that disagree are drawn apart, and opening ' + 'the page writes nothing', () async { final page = _page('page-1', 0); - // Teammates moved the shared origin and the shared landing at the same - // time, so the rows carrying each disagree. Row revisions rank nothing: - // the row with the newest origin has the lowest. - final container = await openCloudPage(page, [ + // A change to the shared origin reached link-1 but not link-2, and one + // to the shared landing reached link-3 but not link-2. + final rows = [ _lineupRow(page.publicId, 'link-1', - origin: _originJson('origin', const Offset(100, 100), 5), + origin: _originJson('origin', const Offset(100, 100)), landing: _landingJson('landing-1'), revision: 1), _lineupRow(page.publicId, 'link-2', - origin: _originJson('origin', const Offset(200, 200), 3), - landing: _landingJson('landing', const Offset(220, 220), 3), + origin: _originJson('origin', const Offset(200, 200)), + landing: _landingJson('landing', const Offset(220, 220)), revision: 9, sortIndex: 1), _lineupRow(page.publicId, 'link-3', origin: _originJson('origin-3'), - landing: _landingJson('landing', const Offset(330, 330), 3), + landing: _landingJson('landing', const Offset(330, 330)), revision: 2, sortIndex: 2), - ]); + ]; + final container = await openCloudPage(page, rows); + // One spot per value: the value of the smallest lineup id carrying the + // spot keeps its id, the other is drawn under an alias. Each lineup is + // attached to its own copy, its marker following the alias. final lineUps = container.read(lineUpProvider); - expect( - lineUps.links.map((link) => link.id), ['link-1', 'link-2', 'link-3']); - expect(lineUps.origins.map((origin) => origin.id), ['origin', 'origin-3']); - // Version 5 outranks version 3, whatever the rows' revisions. + expect(lineUps.origins.map((origin) => origin.id), + ['origin', 'origin@link-2', 'origin-3']); + expect(lineUps.landings.map((landing) => landing.id), + ['landing-1', 'landing', 'landing@link-3']); + expect([ + for (final link in lineUps.links) (link.id, link.originId, link.landingId) + ], [ + ('link-1', 'origin', 'landing-1'), + ('link-2', 'origin@link-2', 'landing'), + ('link-3', 'origin-3', 'landing@link-3'), + ]); expect( lineUps.originById('origin')!.agent.position, const Offset(100, 100)); - expect(lineUps.landings.map((landing) => landing.id), - ['landing-1', 'landing']); - // Both copies are at version 3: link-3's id is the greater. + final aliasedOrigin = lineUps.originById('origin@link-2')!.agent; + expect(aliasedOrigin.position, const Offset(200, 200)); + expect(aliasedOrigin.lineUpID, 'origin@link-2'); expect(lineUps.landingById('landing')!.ability.position, - const Offset(330, 330)); + const Offset(220, 220)); + final aliasedLanding = lineUps.landingById('landing@link-3')!.ability; + expect(aliasedLanding.position, const Offset(330, 330)); + expect(aliasedLanding.lineUpID, 'landing@link-3'); - // link-2 holds copies of both spots other than the drawn ones, so it is - // written with them, at their versions, against its own revision. The - // rows already holding the drawn copies are left be. + // Opening the page writes no lineup, nor does an unrelated edit. await _settle(); Map queuedLineupOps() => { for (final entry in container @@ -4807,33 +4858,29 @@ void main() { if (entry.key.kind == EntitySyncKeyKind.lineup) entry.key: entry.value.pending.op, }; - final link2 = EntitySyncKey.lineup(page.publicId, 'link-2'); - expect(queuedLineupOps().keys, [link2]); - final heal = queuedLineupOps()[link2]! as LineupPatchOp; - expect(heal.expectedLineupRevision, 9); - void expectDrawnCopies(CloudPayload payload) { - final data = cloudPayloadData(payload); - final origin = data['origin'] as Map; - final landing = data['landing'] as Map; - expect(origin['version'], 5); - expect((origin['agent'] as Map)['position'], {'dx': 100.0, 'dy': 100.0}); - expect(landing['version'], 3); - expect( - (landing['ability'] as Map)['position'], {'dx': 330.0, 'dy': 330.0}); - } - - expectDrawnCopies(heal.payload!); + expect(queuedLineupOps(), isEmpty); + expect(lineupOpsAfterTextEdit(container, page), isEmpty); - // A real edit to that lineup carries the drawn copies with it. + // An edit to link-2 writes its row with its own copies, under the real + // ids, against its own revision. container .read(lineUpProvider.notifier) .updateLink(lineUps.linkById('link-2')!.copyWith(notes: 'jump throw')); await _settle(); + final link2 = EntitySyncKey.lineup(page.publicId, 'link-2'); expect(queuedLineupOps().keys, [link2]); final patch = queuedLineupOps()[link2]! as LineupPatchOp; expect(patch.expectedLineupRevision, 9); - expect(cloudPayloadData(patch.payload)['notes'], 'jump throw'); - expectDrawnCopies(patch.payload!); + final data = cloudPayloadData(patch.payload); + expect(data['notes'], 'jump throw'); + final origin = data['origin'] as Map; + expect(origin['id'], 'origin'); + expect((origin['agent'] as Map)['lineUpID'], 'origin'); + expect((origin['agent'] as Map)['position'], {'dx': 200.0, 'dy': 200.0}); + final landing = data['landing'] as Map; + expect(landing['id'], 'landing'); + expect((landing['ability'] as Map)['lineUpID'], 'landing'); + expect((landing['ability'] as Map)['position'], {'dx': 220.0, 'dy': 220.0}); await _settle(); }); @@ -5006,6 +5053,7 @@ void main() { const placedAt = Offset(100, 100); late _FakeStrategyOpQueueNotifier queue; late _FakeServer server; + late _ServerRepository repository; /// Page settings that round-trip exactly, so live sync authors nothing /// for them; the agent size marks which server revision is on screen. @@ -5237,7 +5285,6 @@ void main() { server.teammateEdit(link.id, (data) { final origin = data['origin'] as Map; (origin['agent'] as Map)['weapon'] = 'vandal'; - _bumpVersion(origin); }); remote.setSnapshot(serverSnapshot(page, server.rows, 4)); await settleSync(container, 4); @@ -5430,8 +5477,6 @@ void main() { final landing = cloudPayloadData(op.payload)['landing'] as Map; expect((landing['ability'] as Map)['position'], {'dx': moved.dx, 'dy': moved.dy}); - // The moved landing outranks every copy the rows hold. - expect(landing['version'], 2); } lineups = await land(container, remote, page, contentRevision: 4); @@ -5440,8 +5485,8 @@ void main() { {a.id: 3, b.id: 2, other.id: 1}, ); expect( - {for (final row in lineups) row.publicId: _endsOf(row).landingVersion}, - {a.id: 2, b.id: 2, other.id: 1}, + {for (final row in lineups) row.publicId: _endsOf(row).landingAt}, + {a.id: moved, b.id: moved, other.id: const Offset(300, 300)}, ); final hydrated = container.read(lineUpProvider); expect(hydrated.landingById(a.landingId)!.ability.position, moved); @@ -5929,7 +5974,6 @@ void main() { final landing = data['landing'] as Map; final ability = landing['ability'] as Map; (ability['visualState'] as Map)['showRangeFill'] = false; - _bumpVersion(landing); }, ); @@ -5963,7 +6007,6 @@ void main() { teammate: (data) { final origin = data['origin'] as Map; (origin['agent'] as Map)['weapon'] = 'vandal'; - _bumpVersion(origin); }, ); @@ -6025,7 +6068,6 @@ void main() { server.teammateEdit(link.id, (data) { final origin = data['origin'] as Map; (origin['agent'] as Map)['weapon'] = 'vandal'; - _bumpVersion(origin); }); remote.pageCatalog[first.publicId] = _pageSnapshot( first, @@ -6094,14 +6136,25 @@ void main() { for (var i = 0; i < 10; i++) { await _settle(); } - // The server draws the moved origin for all three lineups now (A's and - // B's copies outrank C's), but C's row still holds the old one. - final drawn = lineUpGraphFromRemoteLineups(server.liveRows); - expect(drawn.origins.single.agent.position, moved); - expect(_endsOf(server.row(c.id)).originVersion, 1); + // The rows disagree now: C's still holds the old origin. + expect( + { + for (final row in server.liveRows) row.publicId: _endsOf(row).originAt + }, + {a.id: moved, b.id: moved, c.id: placedAt}, + ); + + // C's patch is not taken as done by the refresh: it is still queued, + // the same op, until C's row holds the moved origin. + expect(lineupOps(queuedOps()).keys, [cKey]); + expect(queuedOps()[cKey]!.opId, sent[cKey]!.opId); + // Meanwhile the user's canvas keeps the move on all three lineups. + final onScreen = container.read(lineUpProvider); + expect(onScreen.origins.map((origin) => origin.id), [a.originId]); + expect(onScreen.origins.single.agent.position, moved); // The user carries on, so the page syncs against that snapshot. C's - // patch is not taken as done: it stays queued, as it was. + // patch still waits, against C's revision, under the origin's id. container.read(lineUpProvider.notifier).updateLink(container .read(lineUpProvider) .linkById(a.id)! @@ -6112,28 +6165,31 @@ void main() { final patch = waiting[cKey]! as LineupPatchOp; expect(patch.expectedLineupRevision, 1); final origin = cloudPayloadData(patch.payload)['origin'] as Map; - expect(origin['version'], 2); + expect(origin['id'], a.originId); expect((origin['agent'] as Map)['position'], {'dx': moved.dx, 'dy': moved.dy}); - // Its retry lands: every row holds the moved origin, at its version. + // Its retry lands: every row holds the moved origin. await land(container, remote, page, contentRevision: 4); expect( {for (final row in server.liveRows) row.publicId: row.revision}, {a.id: 3, b.id: 2, c.id: 2}, ); for (final row in server.liveRows) { - expect(_endsOf(row).originVersion, 2, reason: row.publicId); - final data = cloudPayloadData(row.payload); - expect(((data['origin'] as Map)['agent'] as Map)['position'], - {'dx': moved.dx, 'dy': moved.dy}, - reason: row.publicId); + expect(_endsOf(row).origin, a.originId, reason: row.publicId); + expect(_endsOf(row).originAt, moved, reason: row.publicId); } // Three adds, three patches of the origin, and the notes; nothing more. expect(server.received, hasLength(7)); expect(lineupOps(queuedOps()), isEmpty); expect(desiredOps(container, page), isEmpty); expect(queue.state.attentionByEntityKey, isEmpty); + + // The rows agree again, so the origin is drawn as one spot. + final hydrated = container.read(lineUpProvider); + expect(hydrated.origins.map((origin) => origin.id), [a.originId]); + expect(hydrated.origins.single.agent.position, moved); + expect(hydrated.links.map((link) => link.originId).toSet(), {a.originId}); await _settle(); }); @@ -6141,114 +6197,48 @@ void main() { const older = Offset(220, 220); /// Lineups A and B into one landing whose rows disagree: A's copy is at - /// [newer], version 5; B's at [older], version 3. B's row has the higher - /// revision, which ranks nothing. + /// [newer], B's at [older]. B's row has the higher revision, which ranks + /// nothing. List divergedFanIn(String pageId) => [ _lineupRow(pageId, 'link-a', origin: _originJson('origin-a'), - landing: _landingJson('landing', newer, 5), + landing: _landingJson('landing', newer), revision: 1), _lineupRow(pageId, 'link-b', origin: _originJson('origin-b', const Offset(60, 20)), - landing: _landingJson('landing', older, 3), + landing: _landingJson('landing', older), revision: 7, sortIndex: 1), ]; - /// The landing the server's rows draw, as anyone opening the page sees. - Offset serverLanding() => lineUpGraphFromRemoteLineups(server.liveRows) - .landings - .single - .ability - .position; - - void expectHealOfB(StrategyOp? op) { - expect(op, isA()); - final heal = op! as LineupPatchOp; - expect(heal.expectedLineupRevision, 7); - final landing = cloudPayloadData(heal.payload)['landing'] as Map; - expect(landing['version'], 5); - expect((landing['ability'] as Map)['position'], - {'dx': newer.dx, 'dy': newer.dy}); - } - - test( - 'opening a page heals a row holding an older copy, once, so deleting ' - 'the lineup with the newer one keeps the spot', () async { - final (container, remote, page) = - await openEmpty(divergedFanIn('page-1')); - LineUpLanding landing() => - container.read(lineUpProvider).landingById('landing')!; - expect(landing().ability.position, newer); - - // Hydration sends B's row the drawn copy at once, against B's revision. - await _settle(); - final heals = lineupOps(queuedOps()); - expect(heals.keys, [keyOf(page, 'link-b')]); - expectHealOfB(heals.values.single); - await land(container, remote, page, contentRevision: 2); - expect(server.row('link-b').revision, 8); - expect(_endsOf(server.row('link-b')).landingVersion, 5); - expect(server.received, hasLength(1)); - - // Once: the healed page syncs and hydrates again with nothing to send. - await container.read(strategyProvider.notifier).notifyCloudMutation(); - await _settle(); - expect(lineupOps(queuedOps()), isEmpty); - - // Deleting A leaves B on the newer copy, here and for anyone opening - // the page. - container.read(lineUpProvider.notifier).deleteLink('link-a'); - await land(container, remote, page, contentRevision: 3); - expect(server.row('link-a').deleted, isTrue); - expect(server.received, hasLength(2)); - expect(serverLanding(), newer); - expect(landing().ability.position, newer); - expect(lineupOps(queuedOps()), isEmpty); - await _settle(); - }); - - test( - 'deleting the lineup with the newer copy while the heal waits sends ' - 'both in one batch', () async { - final (container, remote, page) = - await openEmpty(divergedFanIn('page-1')); - await _settle(); - expect(lineupOps(queuedOps()).keys, [keyOf(page, 'link-b')]); - - container.read(lineUpProvider.notifier).deleteLink('link-a'); - await _settle(); - final batch = lineupOps(queuedOps()); - expect( - batch.keys.toSet(), {keyOf(page, 'link-a'), keyOf(page, 'link-b')}); - expect(batch[keyOf(page, 'link-a')], isA()); - expectHealOfB(batch[keyOf(page, 'link-b')]); + /// Each lineup drawn, with the spot ids it is attached to. + List<(String, String, String)> drawnLinks(ProviderContainer container) => [ + for (final link in container.read(lineUpProvider).links) + (link.id, link.originId, link.landingId), + ]; - await land(container, remote, page, contentRevision: 2); - expect(server.row('link-a').deleted, isTrue); - expect(_endsOf(server.row('link-b')).landingVersion, 5); - expect(serverLanding(), newer); - expect( - container.read(lineUpProvider).landingById('landing')!.ability.position, - newer, - ); - expect(lineupOps(queuedOps()), isEmpty); - await _settle(); - }); + Offset? landingAt(ProviderContainer container, String id) => + container.read(lineUpProvider).landingById(id)?.ability.position; test('opening a page whose copies agree writes no lineup', () async { final (container, remote, page) = await openEmpty([ _lineupRow('page-1', 'link-a', origin: _originJson('origin-a'), - landing: _landingJson('landing', newer, 4), + landing: _landingJson('landing', newer), revision: 3), _lineupRow('page-1', 'link-b', origin: _originJson('origin-b', const Offset(60, 20)), - landing: _landingJson('landing', newer, 4), + landing: _landingJson('landing', newer), revision: 8, sortIndex: 1), ]); await _settle(); + expect(container.read(lineUpProvider).landings.map((l) => l.id), + ['landing']); + expect(drawnLinks(container), [ + ('link-a', 'origin-a', 'landing'), + ('link-b', 'origin-b', 'landing'), + ]); expect(lineupOps(queuedOps()), isEmpty); // Nor does the next hydration. @@ -6258,12 +6248,14 @@ void main() { expect(server.received, isEmpty); }); - test('opening a diverged page heals each stale row once', () async { + test( + 'opening a page whose copies disagree writes nothing and draws each ' + 'copy as its own spot', () async { final (container, remote, page) = await openEmpty([ ...divergedFanIn('page-1'), _lineupRow('page-1', 'link-c', origin: _originJson('origin-c', const Offset(90, 20)), - landing: _landingJson('landing', older, 3), + landing: _landingJson('landing', older), revision: 4, sortIndex: 2), _lineupRow('page-1', 'link-d', @@ -6271,112 +6263,172 @@ void main() { landing: _landingJson('landing-d'), sortIndex: 3), ]); + void expectSplit() { + // A's copy, carried by the smallest lineup id, keeps the landing's + // id; B's and C's, which agree, share one aliased spot. + expect(container.read(lineUpProvider).landings.map((l) => l.id), + ['landing', 'landing@link-b', 'landing-d']); + expect(landingAt(container, 'landing'), newer); + expect(landingAt(container, 'landing@link-b'), older); + expect(drawnLinks(container), [ + ('link-a', 'origin-a', 'landing'), + ('link-b', 'origin-b', 'landing@link-b'), + ('link-c', 'origin-c', 'landing@link-b'), + ('link-d', 'origin-d', 'landing-d'), + ]); + } + + expectSplit(); await _settle(); - final heals = lineupOps(queuedOps()); + expect(lineupOps(queuedOps()), isEmpty); + + // A teammate's unrelated change brings a fresh hydration: the same + // spots are drawn, and still nothing is written. + server.teammateEdit('link-d', (data) => data['notes'] = 'teammate'); + remote.setSnapshot(serverSnapshot(page, server.rows, 2)); + await settleSync(container, 2); expect( - heals.keys.toSet(), {keyOf(page, 'link-b'), keyOf(page, 'link-c')}); + container.read(lineUpProvider).linkById('link-d')!.notes, 'teammate'); + expectSplit(); + expect(lineupOps(queuedOps()), isEmpty); + expect(desiredOps(container, page), isEmpty); + expect(server.received, isEmpty); + }); + + for (final (deleted, kept, keptAt) in [ + ('link-a', 'link-b', older), + ('link-b', 'link-a', newer), + ]) { + test( + 'deleting $deleted from a landing whose copies disagree leaves ' + '$kept on its own copy, its row untouched', () async { + final (container, remote, page) = + await openEmpty(divergedFanIn('page-1')); + final keptRow = server.row(kept); + + container.read(lineUpProvider.notifier).deleteLink(deleted); + await _settle(); + // Only the deletion is written. + final ops = lineupOps(queuedOps()); + expect(ops.keys, [keyOf(page, deleted)]); + expect(ops.values.single, isA()); + expect(drawnLinks(container).single.$1, kept); + expect( + landingAt(container, drawnLinks(container).single.$3), + keptAt, + ); + + await land(container, remote, page, contentRevision: 2); + expect(server.row(deleted).deleted, isTrue); + expect(server.received, hasLength(1)); + // The kept lineup's row is as it was, its own copy and all. + expect(server.row(kept).revision, keptRow.revision); + expect( + canonicalCloudJsonEncode(server.row(kept).payload), + canonicalCloudJsonEncode(keptRow.payload), + ); + // Drawn on its copy, under the landing's own id now nothing + // disagrees with it, here and for anyone opening the page. + expect(drawnLinks(container).single.$3, 'landing'); + expect(landingAt(container, 'landing'), keptAt); + expect( + lineUpGraphFromRemoteLineups(server.liveRows) + .landings + .single + .ability + .position, + keptAt, + ); + expect(lineupOps(queuedOps()), isEmpty); + expect(desiredOps(container, page), isEmpty); + await _settle(); + }); + } + + test( + 'moving one copy of a split spot patches only the rows carrying it, ' + 'and once they agree the spot is drawn as one', () async { + final (container, remote, page) = await openEmpty([ + ...divergedFanIn('page-1'), + _lineupRow('page-1', 'link-c', + origin: _originJson('origin-c', const Offset(90, 20)), + landing: _landingJson('landing', older), + revision: 4, + sortIndex: 2), + ]); + const moved = Offset(500, 500); + + /// The landing [op] writes: under the real id, its marker naming it, + /// at [moved]. + void expectMovedLanding(StrategyOp op) { + final landing = + cloudPayloadData((op as LineupPatchOp).payload)['landing'] as Map; + expect(landing['id'], 'landing'); + expect((landing['ability'] as Map)['lineUpID'], 'landing'); + expect((landing['ability'] as Map)['position'], + {'dx': moved.dx, 'dy': moved.dy}); + } + + // The aliased copy, B's and C's: only their rows, each against its + // own revision. + container + .read(lineUpProvider.notifier) + .updateLandingPosition('landing@link-b', moved); + var ops = lineupOps(desiredOps(container, page)); expect( { - for (final op in heals.values.cast()) + for (final op in ops.values.cast()) op.lineupPublicId: op.expectedLineupRevision, }, {'link-b': 7, 'link-c': 4}, ); + ops.values.forEach(expectMovedLanding); await land(container, remote, page, contentRevision: 2); - expect(server.received, hasLength(2)); expect( {for (final row in server.liveRows) row.publicId: row.revision}, - {'link-a': 1, 'link-b': 8, 'link-c': 5, 'link-d': 1}, + {'link-a': 1, 'link-b': 8, 'link-c': 5}, ); + // A's copy still differs, so the spot is still drawn split. + expect(landingAt(container, 'landing'), newer); + expect(landingAt(container, 'landing@link-b'), moved); - // A teammate's unrelated change brings a fresh hydration: nothing more - // is written. - server.teammateEdit('link-d', (data) => data['notes'] = 'teammate'); - remote.setSnapshot(serverSnapshot(page, server.rows, 3)); - await settleSync(container, 3); + // The real-id copy, A's: only A's row. + container + .read(lineUpProvider.notifier) + .updateLandingPosition('landing', moved); + ops = lineupOps(desiredOps(container, page)); + expect(ops.keys, [keyOf(page, 'link-a')]); + expect((ops.values.single as LineupPatchOp).expectedLineupRevision, 1); + expectMovedLanding(ops.values.single); + await land(container, remote, page, contentRevision: 3); expect( - container.read(lineUpProvider).linkById('link-d')!.notes, 'teammate'); - expect(lineupOps(queuedOps()), isEmpty); - expect(server.received, hasLength(2)); - }); - - test('two teammates healing the same row both land, the second as a noop', - () async { - final (container, remote, page) = - await openEmpty(divergedFanIn('page-1')); - final mine = queue; - // A teammate opens the page before either heal has landed. - final theirRemote = _FakeRemoteEditorNotifier(serverSnapshot( - page, - server.rows, - 1, - )); - final theirQueue = _FakeStrategyOpQueueNotifier(); - final theirs = - await _cloudContainer(remote: theirRemote, queue: theirQueue); - await theirs - .read(strategyPageSessionProvider.notifier) - .initializeForStrategy( - strategyId: 'cloud-strategy', - source: StrategySource.cloud, - selectFirstPageIfNeeded: true, - ); - await _settle(); - Map healsOf( - _FakeStrategyOpQueueNotifier queue) => - lineupOps({ - for (final entry in queue.state.queuedByEntityKey.entries) - entry.key: entry.value.pending.op, - }); - final theirHeals = healsOf(theirQueue); - expect(healsOf(mine).keys, [keyOf(page, 'link-b')]); - expect(theirHeals.keys, [keyOf(page, 'link-b')]); - expectHealOfB(theirHeals.values.single); - - // Mine lands first. - await land(container, remote, page, contentRevision: 2); - expect(server.row('link-b').revision, 8); + {for (final row in server.liveRows) row.publicId: row.revision}, + {'link-a': 2, 'link-b': 8, 'link-c': 5}, + ); - // Theirs, sent against the revision before mine, finds the row - // already holding it: a noop, not a conflict. - final theirSent = { - for (final entry in theirQueue.state.queuedByEntityKey.entries) - entry.key: entry.value.pending.op, - }; - final theirAcks = server.applyBatch(theirSent.values); + // Every copy agrees: one spot again, every lineup on it. + expect(container.read(lineUpProvider).landings.map((l) => l.id), + ['landing']); + expect(landingAt(container, 'landing'), moved); expect( - theirAcks - .firstWhere((ack) => ack.opId == theirHeals.values.single.opId), - isA().having((ack) => ack.currentRevision, 'revision', 8), + drawnLinks(container).map((link) => link.$3).toSet(), + {'landing'}, ); - theirRemote.initialSnapshot = serverSnapshot(page, server.rows, 2); - theirQueue.answer(theirSent, theirAcks); - for (var i = 0; i < 10; i++) { - await _settle(); - } - expect(theirs.read(strategySettingsProvider).agentSize, 32, - reason: 'their page rehydrated from the server'); - for (final (client, clientQueue) in [ - (container, mine), - (theirs, theirQueue), - ]) { - expect(clientQueue.state.attentionByEntityKey, isEmpty); - expect(client.read(strategyConflictProvider), isEmpty); - expect(healsOf(clientQueue), isEmpty); - expect( - client.read(lineUpProvider).landingById('landing')!.ability.position, - newer, - ); - } - expect(server.row('link-b').revision, 8); - expect(server.received, hasLength(2)); + expect(server.received, hasLength(3)); + expect(lineupOps(queuedOps()), isEmpty); + expect(desiredOps(container, page), isEmpty); + await _settle(); }); - /// Opens [page] on the real queue, which sends to [server] and reads it - /// back after every batch. Returns the container and the batches sent. + /// Opens [page] on the real queue, its outbox kept in [store], which + /// sends to [server] through [repository] and reads it back after every + /// batch. A batch waits for [hold] while it is set. Returns the + /// container and the batches sent. Future<(ProviderContainer, List>)> openOnRealQueue( - RemotePage page, - ) async { + RemotePage page, { + DurableStrategyOutboxStore? store, + Completer? hold, + }) async { RemoteEditorSnapshot read() => _editorSnapshot( pages: [page], activePage: _pageSnapshot( @@ -6387,14 +6439,14 @@ void main() { ), ); final remote = _FakeRemoteEditorNotifier(read()); - final repository = _ServerRepository( + repository = _ServerRepository( server, afterBatch: () => remote.initialSnapshot = read(), - ); + )..hold = hold; final container = ProviderContainer(overrides: [ remoteEditorSnapshotProvider.overrideWith(() => remote), durableStrategyOutboxStoreProvider - .overrideWithValue(MemoryDurableStrategyOutboxStore()), + .overrideWithValue(store ?? MemoryDurableStrategyOutboxStore()), convexStrategyRepositoryProvider.overrideWithValue(repository), authProvider.overrideWith(_CloudReadyAuthProvider.new), convexConnectionSnapshotProvider.overrideWithValue(true), @@ -6427,25 +6479,30 @@ void main() { return (container, repository.batches); } + /// Every op sent for [key] in [batches], in order. + List sentFor( + List> batches, + EntitySyncKey key, + ) => + [ + for (final batch in batches) + ...batch.where((op) => EntitySyncKey.forStrategyOp(op) == key), + ]; + /// The edit to [key] the server refused as deleted waits in attention, - /// with its conflict; Keep mine then sends it again. Returns the op it - /// sent, once it has landed. - Future refusedThenKeptMine( + /// with its conflict. + Future refusedAsDeleted( ProviderContainer container, List> batches, EntitySyncKey key, ) async { StrategyOpQueueState queueState() => container.read(strategyOpQueueProvider); - List sentFor() => [ - for (final batch in batches) - ...batch.where((op) => EntitySyncKey.forStrategyOp(op) == key), - ]; await _until(() => queueState().attentionByEntityKey.containsKey(key)); for (var i = 0; i < 10; i++) { await _settle(); } - expect(sentFor().single.kind, StrategyOpKind.patch); + expect(sentFor(batches, key).single.kind, StrategyOpKind.patch); expect( queueState() .lastAckBatch @@ -6455,17 +6512,31 @@ void main() { (ack) => ack.rejectionReason, 'reason', OpRejectionReason.deleted), ); expect(container.read(strategyConflictProvider), hasLength(1)); + } + + /// The edit to [key] the server refused as deleted waits in attention, + /// with its conflict; Keep mine then sends it again. Returns the op it + /// sent, once it has landed. + Future refusedThenKeptMine( + ProviderContainer container, + List> batches, + EntitySyncKey key, + ) async { + StrategyOpQueueState queueState() => + container.read(strategyOpQueueProvider); + List sent() => sentFor(batches, key); + await refusedAsDeleted(container, batches, key); final opQueue = container.read(strategyOpQueueProvider.notifier); await opQueue.retryRejected(flushImmediately: false); await opQueue.flushNow(); - await _until(() => queueState().pending.isEmpty && sentFor().length == 2); + await _until(() => queueState().pending.isEmpty && sent().length == 2); for (var i = 0; i < 10; i++) { await _settle(); } expect(queueState().attentionByEntityKey, isEmpty); expect(queueState().needsAttention, isFalse); - return sentFor().last; + return sent().last; } test( @@ -6538,6 +6609,254 @@ void main() { expect(onScreen()?.position, const Offset(300, 300)); expect(onScreen()?.text, 'hello'); }); + + /// After [refusedAsDeleted], the user deletes the item on the canvas + /// with [delete]. Both sides agree it is gone, so the refused change is + /// settled, in the queue and in [store], and Keep mine then brings + /// nothing back. + Future deletedAfterRefusal( + ProviderContainer container, + List> batches, + MemoryDurableStrategyOutboxStore store, + EntitySyncKey key, + void Function() delete, + ) async { + StrategyOpQueueState queueState() => + container.read(strategyOpQueueProvider); + Iterable stored() => + store.load().records.where((record) => record.entityKey == key); + await refusedAsDeleted(container, batches, key); + expect(stored().single.status, DurableOutboxStatus.attention); + + delete(); + await _until(() => + !queueState().attentionByEntityKey.containsKey(key) && + queueState().pending.isEmpty); + for (var i = 0; i < 10; i++) { + await _settle(); + } + expect(queueState().attentionByEntityKey, isEmpty); + expect(queueState().successorByEntityKey, isEmpty); + expect(queueState().needsAttention, isFalse); + expect(stored(), isEmpty); + // Its refusal is not announced after the fact either. + expect(container.read(strategyConflictProvider), isEmpty); + + // Keep mine has nothing left to send. + final opQueue = container.read(strategyOpQueueProvider.notifier); + await opQueue.retryRejected(flushImmediately: false); + await opQueue.flushNow(); + for (var i = 0; i < 10; i++) { + await _settle(); + } + expect( + sentFor(batches, key).where((op) => op.kind == StrategyOpKind.add), + isEmpty, + ); + expect(queueState().pending, isEmpty); + expect(stored(), isEmpty); + } + + test( + 'a lineup the user deletes after a teammate deleted it and their ' + 'edit was refused stays deleted through Keep mine', () async { + final page = _page('page-1', 0); + server = + _FakeServer(page.publicId, lineups: [_lineup(page.publicId, 'a')]); + final store = MemoryDurableStrategyOutboxStore(); + final (container, batches) = await openOnRealQueue(page, store: store); + LineUpLink? onScreen() => + container.read(lineUpProvider).linkById('link-a'); + + // A teammate deletes the lineup while the user edits its notes. + server.teammateDelete('link-a'); + container + .read(lineUpProvider.notifier) + .updateLink(onScreen()!.copyWith(notes: 'mine')); + await deletedAfterRefusal( + container, + batches, + store, + _lineupKey(page.publicId, 'a'), + () => container.read(lineUpProvider.notifier).deleteLink('link-a'), + ); + + expect(onScreen(), isNull); + expect(server.row('link-a').deleted, isTrue); + expect(server.row('link-a').revision, 2); + expect(server.liveRows, isEmpty); + }); + + test( + 'an element the user deletes after a teammate deleted it and their ' + 'move was refused stays deleted through Keep mine', () async { + final page = _page('page-1', 0); + server = _FakeServer(page.publicId, + elements: [_textElement(page.publicId, 'text-a', 'hello')]); + final store = MemoryDurableStrategyOutboxStore(); + final (container, batches) = await openOnRealQueue(page, store: store); + PlacedText? onScreen() => container + .read(textProvider) + .where((text) => text.id == 'text-a') + .firstOrNull; + + // A teammate deletes the text while the user moves it. + server.teammateDeleteElement('text-a'); + container + .read(textProvider.notifier) + .updatePosition(const Offset(300, 300), 'text-a'); + await deletedAfterRefusal( + container, + batches, + store, + EntitySyncKey.element(page.publicId, 'text-a'), + () => + container.read(textProvider.notifier).removeTextAsAction('text-a'), + ); + + expect(onScreen(), isNull); + expect(server.element('text-a').deleted, isTrue); + expect(server.element('text-a').revision, 2); + }); + + test( + 'a recovered edit to a lineup on a split spot is sent as it was, and ' + 'opening the page writes nothing else', () async { + final page = _page('page-1', 0); + server = + _FakeServer(page.publicId, lineups: divergedFanIn(page.publicId)); + final rowA = server.row('link-a'); + final rowB = server.row('link-b'); + final bKey = keyOf(page, 'link-b'); + // Before a restart, the user's notes edit to B was saved to the outbox + // but never sent. + final recovered = LineupPatchOp( + opId: 'recovered-notes', + lineupPublicId: 'link-b', + pagePublicId: page.publicId, + payload: + _editedLineup(rowB, (data) => data['notes'] = 'recovered').payload, + expectedLineupRevision: rowB.revision, + ); + final store = MemoryDurableStrategyOutboxStore(); + await store.put(DurableOutboxRecord( + accountId: 'account-a', + strategyPublicId: 'cloud-strategy', + entityKey: bKey, + pending: PendingOp(op: recovered, clientId: 'client-before-restart'), + status: DurableOutboxStatus.queued, + createdAt: DateTime.utc(2026), + updatedAt: DateTime.utc(2026), + )); + + // Its send waits while the page opens. + final hold = Completer(); + final (container, batches) = + await openOnRealQueue(page, store: store, hold: hold); + for (var i = 0; i < 10; i++) { + await _settle(); + } + StrategyOpQueueState queueState() => + container.read(strategyOpQueueProvider); + bool isLineup(StrategyOp op) => + EntitySyncKey.forStrategyOp(op)?.kind == EntitySyncKeyKind.lineup; + + // The page draws B on its own copy of the landing, and the recovered + // edit is the only lineup work: nothing replaced it, nothing joined it. + expect(container.read(lineUpProvider).landings.map((l) => l.id), + ['landing', 'landing@link-b']); + expect( + [ + for (final pending in queueState().pending) + if (isLineup(pending.op)) pending.op.opId, + ], + ['recovered-notes'], + ); + expect( + [for (final record in store.load().records) record.pending.op.opId], + ['recovered-notes'], + ); + + hold.complete(); + await _until(() => queueState().pending.isEmpty); + for (var i = 0; i < 10; i++) { + await _settle(); + } + + // Only the recovered edit was ever sent for a lineup, as it was saved. + expect( + [ + for (final batch in batches) + for (final op in batch) + if (isLineup(op)) op.opId, + ], + ['recovered-notes'], + ); + expect(server.received.single.payload, recovered.payload); + expect(server.row('link-b').revision, rowB.revision + 1); + expect( + cloudPayloadData(server.row('link-b').payload)['notes'], 'recovered'); + expect(server.row('link-a').revision, rowA.revision); + expect( + canonicalCloudJsonEncode(server.row('link-a').payload), + canonicalCloudJsonEncode(rowA.payload), + ); + expect( + container.read(lineUpProvider).linkById('link-b')!.notes, + 'recovered', + ); + expect(landingAt(container, 'landing@link-b'), older); + expect(landingAt(container, 'landing'), newer); + }); + + test( + 'an edit whose answer was lost is answered at the revision it landed ' + 'at, so the edit behind it cannot overwrite a teammate', () async { + final page = _page('page-1', 0); + server = + _FakeServer(page.publicId, lineups: [_lineup(page.publicId, 'a')]); + final hold = Completer(); + final (container, batches) = await openOnRealQueue(page, hold: hold); + final key = _lineupKey(page.publicId, 'a'); + StrategyOpQueueState queueState() => + container.read(strategyOpQueueProvider); + LineUpLink onScreen() => + container.read(lineUpProvider).linkById('link-a')!; + + // The user's notes edit reaches the server, which takes it at + // revision 2, but the answer is lost; a teammate renames the lineup + // right after. + repository.loseNextAnswer = () => + server.teammateEdit('link-a', (data) => data['name'] = 'theirs'); + container + .read(lineUpProvider.notifier) + .updateLink(onScreen().copyWith(notes: 'one')); + await _until(() => queueState().inFlightByEntityKey.containsKey(key)); + // While it is on its way, the user edits again; that waits behind it. + container + .read(lineUpProvider.notifier) + .updateLink(onScreen().copyWith(notes: 'two')); + await _until(() => queueState().successorByEntityKey.containsKey(key)); + hold.complete(); + + // The first edit is sent again and answered as landed at revision 2, + // so the second goes against revision 2: refused, not stored over the + // teammate's name. + await _until(() => queueState().attentionByEntityKey.containsKey(key)); + for (var i = 0; i < 10; i++) { + await _settle(); + } + final sent = sentFor(batches, key); + expect(sent, hasLength(3)); + expect(sent[1].opId, sent[0].opId); + expect(cloudPayloadData(sent[2].payload)['notes'], 'two'); + expect((sent[2] as LineupPatchOp).expectedLineupRevision, 2); + final row = server.row('link-a'); + expect(row.revision, 3); + expect(cloudPayloadData(row.payload)['name'], 'theirs'); + expect(cloudPayloadData(row.payload)['notes'], 'one'); + expect(container.read(strategyConflictProvider), hasLength(1)); + }); }); group('lineup history across rehydration', () { diff --git a/tool/convex_client_gauntlet/runtime/lib/workload.dart b/tool/convex_client_gauntlet/runtime/lib/workload.dart index 4bb8e864..37130caa 100644 --- a/tool/convex_client_gauntlet/runtime/lib/workload.dart +++ b/tool/convex_client_gauntlet/runtime/lib/workload.dart @@ -285,7 +285,6 @@ Map _lineupPayload(int seed, int cycle, int variant) { 'kind': 'plain', 'lineUpID': '$id-origin', }, - 'version': 1, }, 'landing': { 'id': '$id-landing', @@ -299,7 +298,6 @@ Map _lineupPayload(int seed, int cycle, int variant) { 'length': 0, 'lineUpID': '$id-landing', }, - 'version': 1, }, }, }; From 1d5ce1d626062d1b9d8c602e5bafaa6465f5993c Mon Sep 17 00:00:00 2001 From: Dara Adedeji Date: Fri, 2 Oct 2026 18:08:34 -0400 Subject: [PATCH 07/14] Sync each lineup group as one cloud row Lineups that share a standing or landing spot now live in one cloud row with those spots (kind "lineups": origins, landings, links). A shared spot exists once, so copies can no longer disagree, and the row's one revision guards every lineup on it: two people changing the same group meet as an ordinary conflict. Nothing points across rows. A group only grows or merges, never splits, and a new group is named after its smallest lineup id that no group of the page has used. This replaces the copy-per-lineup model of the earlier commits, which failed review three times because copies of a spot can drift apart. - Server: group validation (every lineup aims inside its row), one lineupAgents row per origin, images across all links, duplicate copies groups. A replayed op with no recorded revision answers with none, so no successor is promoted onto a guessed revision. - Client: per-page group memory in live sync, holds take whole groups, migration uploads groups. - Conflicts on lineup groups list what each side changed, and offer Keep both: the cloud's group stays and the user's version is added beside it under fresh ids. It loads the cloud version first, so failing offline changes nothing. - settleAttention only drops work refused because a teammate deleted it. Co-Authored-By: Claude Opus 5.5 (1M context) --- convex/function_spec.json | 14 +- convex/imageAssetLifecycle.test.ts | 39 +- convex/lib/cloudProtocol.ts | 3 +- convex/lib/imageAssets.ts | 26 +- convex/lib/payloadValidators.ts | 21 +- convex/lib/strategyAgentSummary.ts | 88 +- convex/lineups.test.ts | 1043 +++++----- convex/maintenance.ts | 4 +- convex/mediaLimits.test.ts | 34 +- convex/ops.ts | 95 +- convex/pageTrash.test.ts | 33 +- convex/referencedAssetIds.test.ts | 4 +- convex/schema.ts | 19 +- convex/strategies.ts | 48 +- convex/strategyDuplicate.test.ts | 474 ++--- convex/syncBoundaries.test.ts | 70 +- convex/testContent.helpers.ts | 123 +- lib/collab/cloud_lineup_rows.dart | 402 ++-- lib/collab/cloud_payload_upgrade.dart | 2 +- lib/collab/collab_models.dart | 13 +- lib/collab/convex_payload_codecs.dart | 11 +- lib/collab/generated/convex_models.dart | 24 +- lib/const/line_provider.dart | 13 + .../active_page_live_sync_provider.dart | 66 +- .../collab/lineup_conflicts_provider.dart | 95 + .../collab/strategy_op_queue_provider.dart | 24 +- .../strategy_page_session_provider.dart | 70 + lib/strategy/lineup_group_changes.dart | 136 ++ lib/strategy/remote_page_merge.dart | 30 +- lib/strategy/strategy_cloud_migration.dart | 22 +- lib/strategy/strategy_page_apply.dart | 7 +- lib/strategy/strategy_page_source.dart | 4 +- lib/widgets/cloud_sync_button.dart | 157 +- test/cloud_lineup_rows_test.dart | 561 ++--- test/cloud_payload_upgrade_test.dart | 2 +- test/collab_sync_models_test.dart | 55 +- test/global_strategy_outbox_test.dart | 13 +- test/lineup_group_changes_test.dart | 139 ++ test/remote_page_merge_test.dart | 85 +- test/strategy_cloud_migration_test.dart | 39 +- test/strategy_op_queue_provider_test.dart | 77 +- test/strategy_page_session_provider_test.dart | 1828 +++++++++++------ test/widgets/cloud_sync_button_test.dart | 149 +- 43 files changed, 3889 insertions(+), 2273 deletions(-) create mode 100644 lib/providers/collab/lineup_conflicts_provider.dart create mode 100644 lib/strategy/lineup_group_changes.dart create mode 100644 test/lineup_group_changes_test.dart diff --git a/convex/function_spec.json b/convex/function_spec.json index 10bf49b0..1a8a6711 100644 --- a/convex/function_spec.json +++ b/convex/function_spec.json @@ -44665,7 +44665,7 @@ "kind": { "fieldType": { "type": "literal", - "value": "lineup" + "value": "lineups" }, "optional": false }, @@ -48008,7 +48008,7 @@ "kind": { "fieldType": { "type": "literal", - "value": "lineup" + "value": "lineups" }, "optional": false }, @@ -91406,7 +91406,7 @@ "kind": { "fieldType": { "type": "literal", - "value": "lineup" + "value": "lineups" }, "optional": false }, @@ -104496,7 +104496,7 @@ "kind": { "fieldType": { "type": "literal", - "value": "lineup" + "value": "lineups" }, "optional": false }, @@ -137511,7 +137511,7 @@ "kind": { "fieldType": { "type": "literal", - "value": "lineup" + "value": "lineups" }, "optional": false }, @@ -160827,7 +160827,7 @@ "kind": { "fieldType": { "type": "literal", - "value": "lineup" + "value": "lineups" }, "optional": false }, @@ -186398,7 +186398,7 @@ "kind": { "fieldType": { "type": "literal", - "value": "lineup" + "value": "lineups" }, "optional": false }, diff --git a/convex/imageAssetLifecycle.test.ts b/convex/imageAssetLifecycle.test.ts index 7f88a0bf..d49f7c57 100644 --- a/convex/imageAssetLifecycle.test.ts +++ b/convex/imageAssetLifecycle.test.ts @@ -14,7 +14,7 @@ import schema from "./schema"; import { insertElement, insertLineup, - lineupPayload, + lineupsPayload, } from "./testContent.helpers"; import { modules } from "./test.setup"; @@ -236,14 +236,30 @@ describe("image asset lifecycle", () => { updatedAt: now, }); } + // One lineup group whose two lineups each show an image. await insertLineup(ctx, { publicId: "remaining-lineup", strategyId: strategy._id, pageId: pageBId, - payloadKind: "lineup", + payloadKind: "lineups", payloadVersion: 1, - payload: lineupPayload("remaining-lineup", { - images: [{ id: "still-used" }], + payload: lineupsPayload("remaining-lineup", { + origins: [{ id: "origin" }], + landings: [{ id: "landing" }, { id: "other-landing" }], + links: [ + { + id: "remaining-lineup", + originId: "origin", + landingId: "landing", + images: [{ id: "still-used" }], + }, + { + id: "remaining-link", + originId: "origin", + landingId: "other-landing", + images: [{ id: "still-used-by-link", fileExtension: ".png" }], + }, + ], }), sortIndex: 0, revision: 1, @@ -251,21 +267,6 @@ describe("image asset lifecycle", () => { createdAt: now, updatedAt: now, }); - await insertLineup(ctx, { - publicId: "remaining-link", - strategyId: strategy._id, - pageId: pageBId, - payloadKind: "lineup", - payloadVersion: 1, - payload: lineupPayload("remaining-link", { - images: [{ id: "still-used-by-link", fileExtension: ".png" }], - }), - sortIndex: 1, - revision: 1, - deleted: false, - createdAt: now, - updatedAt: now, - }); await insertElement(ctx, { publicId: "remaining-image-element", strategyId: strategy._id, diff --git a/convex/lib/cloudProtocol.ts b/convex/lib/cloudProtocol.ts index b3ad5b28..410181be 100644 --- a/convex/lib/cloudProtocol.ts +++ b/convex/lib/cloudProtocol.ts @@ -8,7 +8,8 @@ import { v } from "convex/values"; // public query that returns element or lineup payloads, and the shell the // editor opens with, takes the protocol too: a client that cannot read a // row must not see one. -// 5: a lineup is one row of kind "lineup" carrying its origin and landing, +// 5: one row per lineup group, of kind "lineups": the lineups on a page +// joined through shared spots, with their origins, landings and links, // replacing the origin, landing and link rows of 4. A client on 4 cannot // read the new rows and writes rows the server no longer takes, so it is // refused until it reloads into 5. diff --git a/convex/lib/imageAssets.ts b/convex/lib/imageAssets.ts index 0ac94a57..38ad835a 100644 --- a/convex/lib/imageAssets.ts +++ b/convex/lib/imageAssets.ts @@ -52,26 +52,30 @@ export function collectAssetIdFromElementPayload( return typeof payload.data.id === "string" ? payload.data.id : null; } -/// The images a lineup shows: data.images[*].id. Its origin and landing -/// hold none. +/// The images a lineup group shows: links[*].images[*].id, across every +/// lineup in it. Its origins and landings hold none. export function collectAssetIdsFromLineupPayload( payload: Doc<"lineups">["payload"], ): Set { const assetIds = new Set(); - const images = payload.data.images; - if (!Array.isArray(images)) return assetIds; - for (const image of images) { - if ( - typeof image === "object" && - image !== null && - typeof (image as { id?: unknown }).id === "string" - ) { - assetIds.add((image as { id: string }).id); + const links = payload.data.links; + if (!Array.isArray(links)) return assetIds; + for (const link of links) { + const images = isObject(link) ? link.images : undefined; + if (!Array.isArray(images)) continue; + for (const image of images) { + if (isObject(image) && typeof image.id === "string") { + assetIds.add(image.id); + } } } return assetIds; } +function isObject(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + export function collectReferencedAssetIds( elements: Doc<"elements">[], lineups: Doc<"lineups">[], diff --git a/convex/lib/payloadValidators.ts b/convex/lib/payloadValidators.ts index 92400a0c..17f0b112 100644 --- a/convex/lib/payloadValidators.ts +++ b/convex/lib/payloadValidators.ts @@ -114,24 +114,27 @@ export const elementPayloadValidator = v.union( }), ); -// A lineup is one row: its details (name, video, notes, images) and whole -// copies of its origin (the agent) and landing (the ability). Lineups that -// share a spot each carry their own copy of it, and the client draws those -// copies as one spot again (see cloud_lineup_rows.dart). -export const lineupPayloadKindValidator = v.literal("lineup"); +// A lineup group is one row: the lineups on one page joined through shared +// spots, with each spot stored once. data holds the group's id (the row's +// key) and its origins (each placing an agent), landings (each placing an +// ability) and links (one per lineup: which origin to which landing, and its +// name, video, notes and images). See assertLineupPayload in ops.ts for the +// rules a row must meet. +export const lineupPayloadKindValidator = v.literal("lineups"); +export const LINEUPS_PAYLOAD_VERSION = 1; export const lineupPayloadValidator = v.object({ - kind: v.literal("lineup"), + kind: v.literal("lineups"), payloadVersion: v.number(), data: cloudJsonObjectValidator, }); -// What a lineup op may carry as an argument: a lineup, or a row of the graph -// clients on protocol 4 and earlier wrote. Convex checks arguments before +// What a lineup op may carry as an argument: a lineup group, or a row of the +// graph clients on protocol 4 wrote. Convex checks arguments before // the handler runs, so refusing the old kinds here would answer an old // client with a validation error instead of CLIENT_UPGRADE_REQUIRED from the // protocol gate. The handler refuses them (see assertLineupPayload in -// ops.ts) and storage takes only lineups. +// ops.ts) and storage takes only lineup groups. export const lineupOpPayloadValidator = v.union( lineupPayloadValidator, v.object({ diff --git a/convex/lib/strategyAgentSummary.ts b/convex/lib/strategyAgentSummary.ts index 36947cf8..097f1245 100644 --- a/convex/lib/strategyAgentSummary.ts +++ b/convex/lib/strategyAgentSummary.ts @@ -1,5 +1,6 @@ import type { Doc, Id } from "../_generated/dataModel"; import type { MutationCtx, QueryCtx } from "../_generated/server"; +import type { WithoutSystemFields } from "convex/server"; function asRecord(value: unknown): Record | null { return typeof value === "object" && value !== null @@ -14,17 +15,23 @@ function agentTypeOf(data: unknown): string | null { return typeof type === "string" && type.length > 0 ? type : null; } -/// The agent a lineup starts from: its origin's id and agent type, or null -/// for a lineup without one. -export function lineupAgentOf( +/// The agents a lineup group starts from: each origin's id and agent type, +/// skipping an origin whose agent names no type. +export function lineupAgentsOf( payload: Doc<"lineups">["payload"], -): { originId: string; agentType: string } | null { - const origin = asRecord(payload.data.origin); - const agentType = agentTypeOf(origin?.agent); - if (origin === null || typeof origin.id !== "string" || agentType === null) { - return null; +): { originId: string; agentType: string }[] { + const origins = payload.data.origins; + if (!Array.isArray(origins)) return []; + const agents: { originId: string; agentType: string }[] = []; + for (const origin of origins) { + const record = asRecord(origin); + const agentType = agentTypeOf(record?.agent); + if (record === null || typeof record.id !== "string" || agentType === null) { + continue; + } + agents.push({ originId: record.id, agentType }); } - return { originId: origin.id, agentType }; + return agents; } type LineupAgent = Pick< @@ -32,10 +39,10 @@ type LineupAgent = Pick< "pageId" | "originId" | "agentType" >; -/// Brings the lineupAgents row of one lineup in step with it: one row while -/// the lineup is live and starts from an agent, none otherwise. Call after -/// every write of a lineup row, with null once the row itself is gone. -export async function syncLineupAgent( +/// Brings the lineupAgents rows of one lineup group in step with it: one +/// row per origin while the group is live, none otherwise. Call after every +/// write of a lineup row, with null once the row itself is gone. +export async function syncLineupAgents( ctx: MutationCtx, lineupId: Id<"lineups">, lineup: Pick< @@ -46,28 +53,37 @@ export async function syncLineupAgent( const existing = await ctx.db .query("lineupAgents") .withIndex("by_lineupId", (q) => q.eq("lineupId", lineupId)) - .unique(); - const agent = - lineup === null || lineup.deleted ? null : lineupAgentOf(lineup.payload); - if (agent === null || lineup === null) { - if (existing !== null) await ctx.db.delete(existing._id); - return; + .collect(); + // Keyed by origin id, which is unique within a group. + const wanted = new Map>>(); + if (lineup !== null && !lineup.deleted) { + for (const agent of lineupAgentsOf(lineup.payload)) { + wanted.set(agent.originId, { + strategyId: lineup.strategyId, + pageId: lineup.pageId, + lineupId, + ...agent, + }); + } } - const wanted = { - strategyId: lineup.strategyId, - pageId: lineup.pageId, - lineupId, - ...agent, - }; - if (existing === null) { - await ctx.db.insert("lineupAgents", wanted); - } else if ( - existing.strategyId !== wanted.strategyId || - existing.pageId !== wanted.pageId || - existing.originId !== wanted.originId || - existing.agentType !== wanted.agentType - ) { - await ctx.db.patch(existing._id, wanted); + for (const row of existing) { + const want = wanted.get(row.originId); + if (want === undefined) { + // An origin the group no longer holds, or a second row for one. + await ctx.db.delete(row._id); + continue; + } + wanted.delete(row.originId); + if ( + row.strategyId !== want.strategyId || + row.pageId !== want.pageId || + row.agentType !== want.agentType + ) { + await ctx.db.patch(row._id, want); + } + } + for (const row of wanted.values()) { + await ctx.db.insert("lineupAgents", row); } } @@ -113,8 +129,8 @@ export async function refreshStrategyAgentSummary( } /// The agents that live content uses, most used first. Takes any elements -/// and the agents of live lineups; live agent elements count, and each -/// lineup origin once per page, however many lineups share it. +/// and the agents of live lineup groups; live agent elements count, and +/// each lineup origin once per page, however many lineups share it. export function agentTypesOf( elements: Pick, "deleted" | "elementType" | "payload">[], lineupAgents: LineupAgent[], diff --git a/convex/lineups.test.ts b/convex/lineups.test.ts index 4634e5bd..a98251f9 100644 --- a/convex/lineups.test.ts +++ b/convex/lineups.test.ts @@ -11,8 +11,10 @@ import { CURRENT_CLOUD_PROTOCOL_VERSION } from "./lib/cloudProtocol"; import schema from "./schema"; import { insertLineup, - lineupPayload, + lineupsPayload, + oneLineupPayload, type TestLineup, + type TestLineupGroup, } from "./testContent.helpers"; import { modules } from "./test.setup"; @@ -109,12 +111,17 @@ async function createHarness(): Promise<{ return { t, owner, editor, viewer }; } -/** A lineup row as a test sends it: its key and its payload. */ +/** A group row holding one lineup, as a test sends it: its key and payload. */ function lineup(id: string, details: TestLineup = {}) { - return { key: id, payload: lineupPayload(id, details) }; + return { key: id, payload: oneLineupPayload(id, details) }; } -type Row = ReturnType; +/** A lineup group row as a test sends it: its key and its payload. */ +function group(id: string, lineups: TestLineupGroup) { + return { key: id, payload: lineupsPayload(id, lineups) }; +} + +type Row = { key: string; payload: { kind: string; data: object } }; function addOp(row: Row, sortIndex: number, page = pagePublicId) { return { @@ -220,9 +227,9 @@ type LineupAgentRow = { agentType: string; }; -/// A strategy's lineupAgents rows, each named by its lineup's and page's -/// public ids, ordered by lineup. Fails on a row that names a lineup gone -/// or out of step with it (another strategy or page). +/// A strategy's lineupAgents rows, each named by its group's and page's +/// public ids, ordered by group and origin. Fails on a row that names a +/// group gone or out of step with it (another strategy or page). async function lineupAgentRows( t: RootHarness, strategy = strategyPublicId, @@ -255,7 +262,11 @@ async function lineupAgentRows( agentType: row.agentType, }); } - return described.sort((a, b) => a.lineup.localeCompare(b.lineup)); + return described.sort( + (a, b) => + a.lineup.localeCompare(b.lineup) || + a.originId.localeCompare(b.originId), + ); }); } @@ -283,50 +294,63 @@ afterEach(() => { vi.useRealTimers(); }); -/** Two lineups from different origins aiming at one shared landing. */ -const fanIn = [ - lineup("from-heaven", { - originId: "heaven", - landingId: "site", - name: "From heaven", - }), - lineup("from-mid", { originId: "mid", landingId: "site", name: "From mid" }), -]; - -/** Two lineups thrown from one shared origin to different landings. */ -const fanOut = [ - lineup("to-default", { - originId: "spawn", - landingId: "default", - name: "Default plant", - }), - lineup("to-hell", { originId: "spawn", landingId: "hell", name: "Hell" }), -]; - -describe("one row per lineup", () => { - test("a lineup round-trips with its origin and landing intact", async () => { +/// One group: two lineups from different origins into one shared landing +/// (fan-in), and a third from the first one's origin to another landing +/// (fan-out). A fresh copy each call, for a test to change. +function siteLineups(): TestLineupGroup { + return { + origins: [ + { id: "heaven", agentType: "brimstone", position: { dx: 120, dy: 340 } }, + { id: "mid", agentType: "viper", position: { dx: 200, dy: 100 } }, + ], + landings: [ + { id: "site", position: { dx: 400, dy: 220 } }, + { id: "hell", position: { dx: 500, dy: 300 } }, + ], + links: [ + { + id: "from-heaven", + originId: "heaven", + landingId: "site", + name: "From heaven", + youtubeLink: "https://youtu.be/heaven", + notes: "Jump throw", + }, + { id: "from-mid", originId: "mid", landingId: "site", name: "From mid" }, + { + id: "heaven-to-hell", + originId: "heaven", + landingId: "hell", + name: "Hell", + }, + ], + }; +} + +const siteKey = "site-group"; + +function siteGroup(edit: (lineups: TestLineupGroup) => void = () => {}) { + const lineups = siteLineups(); + edit(lineups); + return group(siteKey, lineups); +} + +describe("one row per lineup group", () => { + test("a group round-trips with its origins, landings and links intact", async () => { const { owner } = await createHarness(); - const row = lineup("smoke", { - originId: "smoke-origin", - landingId: "smoke-landing", - agentType: "brimstone", - originPosition: { dx: 120, dy: 340 }, - landingPosition: { dx: 400, dy: 220 }, - name: "A main smoke", - youtubeLink: "https://youtu.be/smoke", - notes: "Jump throw", - }); + const row = siteGroup(); const results = await apply(owner, "owner-client", [addOp(row, 0)]); expect(results[0]).toMatchObject({ status: "applied", appliedRevision: 1 }); const rows = await pageLineups(owner); expect(rows).toHaveLength(1); expect(rows[0]).toMatchObject({ - publicId: "smoke", + publicId: siteKey, revision: 1, deleted: false, }); expect(rows[0]!.payload).toEqual(row.payload); + expect(rows[0]!.payload.kind).toBe("lineups"); const full = (await owner.query(getFullSnapshot, { clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, @@ -341,180 +365,73 @@ describe("one row per lineup", () => { expect(listed.map((entry) => entry.payload)).toEqual([row.payload]); }); - test("lineups sharing a landing each store and read back their own copy", async () => { - const { owner } = await createHarness(); - const results = await apply( - owner, - "owner-client", - fanIn.map((row, index) => addOp(row, index)), - ); - expect(results.map((result) => result.status)).toEqual([ - "applied", - "applied", - ]); - - const rows = byKey(await pageLineups(owner)); - expect([...rows.keys()]).toEqual(["from-heaven", "from-mid"]); - for (const row of fanIn) { - expect(rows.get(row.key)!.payload).toEqual(row.payload); - } - expect(rows.get("from-heaven")!.payload.data.landing).toEqual( - rows.get("from-mid")!.payload.data.landing, - ); - }); - - test("lineups sharing an origin each store and read back their own copy", async () => { - const { owner } = await createHarness(); - const results = await apply( - owner, - "owner-client", - fanOut.map((row, index) => addOp(row, index)), - ); - expect(results.map((result) => result.status)).toEqual([ - "applied", - "applied", - ]); - - const rows = byKey(await pageLineups(owner)); - for (const row of fanOut) { - expect(rows.get(row.key)!.payload).toEqual(row.payload); - } - expect(rows.get("to-default")!.payload.data.origin).toEqual( - rows.get("to-hell")!.payload.data.origin, - ); - }); - - test("a lineup may share its id with its landing", async () => { + test("a group, its lineup and that lineup's landing may share one id", async () => { const { owner } = await createHarness(); - const row = lineup("same-id", { landingId: "same-id" }); + const row = group("same-id", { + origins: [{ id: "origin" }], + landings: [{ id: "same-id" }], + links: [{ id: "same-id", originId: "origin", landingId: "same-id" }], + }); const results = await apply(owner, "owner-client", [addOp(row, 0)]); expect(results[0]).toMatchObject({ status: "applied" }); expect((await pageLineups(owner))[0]!.payload).toEqual(row.payload); }); - test("moving a shared spot patches each lineup that carries it on its own", async () => { + test("moving a shared spot is one patch of its group, seen by every lineup in it", async () => { const { owner } = await createHarness(); - await apply( - owner, - "owner-client", - fanIn.map((row, index) => addOp(row, index)), - ); + await apply(owner, "owner-client", [addOp(siteGroup(), 0)]); const moved = { dx: 10, dy: 20 }; + const edit = siteGroup((lineups) => { + lineups.landings[0]!.position = moved; + }); - const first = await apply(owner, "owner-client", [ - patchOp( - "move-site-heaven", - lineup("from-heaven", { - originId: "heaven", - landingId: "site", - landingPosition: moved, - name: "From heaven", - }), - 1, - ), - ]); - expect(first[0]).toMatchObject({ status: "applied", appliedRevision: 2 }); - // The other row keeps its own copy until its own patch lands. - let rows = byKey(await pageLineups(owner)); - expect(rows.get("from-heaven")).toMatchObject({ revision: 2 }); - expect(rows.get("from-mid")).toMatchObject({ revision: 1 }); - expect(rows.get("from-mid")!.payload).toEqual(fanIn[1]!.payload); - - const second = await apply(owner, "owner-client", [ - patchOp( - "move-site-mid", - lineup("from-mid", { - originId: "mid", - landingId: "site", - landingPosition: moved, - name: "From mid", - }), - 1, - ), + const results = await apply(owner, "owner-client", [ + patchOp("move-site", edit, 1), ]); - expect(second[0]).toMatchObject({ status: "applied", appliedRevision: 2 }); - rows = byKey(await pageLineups(owner)); - for (const key of ["from-heaven", "from-mid"]) { - expect(rows.get(key)).toMatchObject({ revision: 2 }); - expect(rows.get(key)!.payload.data.landing).toMatchObject({ + expect(results[0]).toMatchObject({ status: "applied", appliedRevision: 2 }); + const rows = await pageLineups(owner); + expect(rows).toHaveLength(1); + expect(rows[0]).toMatchObject({ revision: 2 }); + expect(rows[0]!.payload).toEqual(edit.payload); + const data = rows[0]!.payload.data as { + landings: Array<{ id: string; ability: { position: unknown } }>; + links: Array<{ landingId: string }>; + }; + // The spot is stored once, and both lineups into it name it. + expect(data.landings.filter((landing) => landing.id === "site")).toEqual([ + { id: "site", - ability: { position: moved, lineUpID: "site" }, - }); - } + ability: { id: "ability-site", position: moved, lineUpID: "site" }, + }, + ]); + expect(data.links.map((link) => link.landingId)).toEqual([ + "site", + "site", + "hell", + ]); }); - test("teammates editing different lineups that share a spot never conflict", async () => { + test("teammates editing different lineups of one group from the same revision: the second is a conflict, not a silent overwrite", async () => { const { owner, editor } = await createHarness(); - await apply( - owner, - "owner-client", - fanIn.map((row, index) => addOp(row, index)), - ); - - // Both edit from the same loaded revision (1) of their own lineup: the - // owner moves the shared landing on theirs, the editor writes notes on - // the other. + await apply(owner, "owner-client", [addOp(siteGroup(), 0)]); + const ownerEdit = siteGroup((lineups) => { + lineups.links[0]!.name = "Owner name"; + }); const ownerResults = await apply(owner, "owner-client", [ - patchOp( - "owner-moves-site", - lineup("from-heaven", { - originId: "heaven", - landingId: "site", - landingPosition: { dx: 5, dy: 5 }, - name: "From heaven", - }), - 1, - ), - ]); - const editorResults = await apply(editor, "editor-client", [ - patchOp( - "editor-notes-mid", - lineup("from-mid", { - originId: "mid", - landingId: "site", - name: "From mid", - notes: "Editor notes", - }), - 1, - ), + patchOp("owner-renames", ownerEdit, 1), ]); expect(ownerResults[0]).toMatchObject({ status: "applied", appliedRevision: 2, }); - expect(editorResults[0]).toMatchObject({ - status: "applied", - appliedRevision: 2, - }); - - const rows = byKey(await pageLineups(owner)); - expect(rows.get("from-heaven")!.payload.data).toMatchObject({ - notes: "", - landing: { ability: { position: { dx: 5, dy: 5 } } }, - }); - expect(rows.get("from-mid")!.payload.data).toMatchObject({ - notes: "Editor notes", - landing: { ability: { position: { dx: 0, dy: 0 } } }, - }); - }); - - test("a stale edit to the same lineup is a conflict, not a silent overwrite", async () => { - const { owner, editor } = await createHarness(); - await apply(owner, "owner-client", [addOp(fanIn[0]!, 0)]); - const ownerEdit = lineup("from-heaven", { - originId: "heaven", - landingId: "site", - name: "Owner name", - }); - await apply(owner, "owner-client", [patchOp("owner-renames", ownerEdit, 1)]); + // The editor, still on revision 1, writes notes on another lineup of + // the same group. const stale = await apply(editor, "editor-client", [ patchOp( - "editor-renames", - lineup("from-heaven", { - originId: "heaven", - landingId: "site", - name: "Stale name", + "editor-notes", + siteGroup((lineups) => { + lineups.links[1]!.notes = "Editor notes"; }), 1, ), @@ -524,118 +441,204 @@ describe("one row per lineup", () => { reason: "revision_mismatch", current: { type: "lineup", revision: 2, value: ownerEdit.payload }, }); - const row = byKey(await pageLineups(owner)).get("from-heaven"); + const row = byKey(await pageLineups(owner)).get(siteKey); expect(row).toMatchObject({ revision: 2 }); expect(row!.payload).toEqual(ownerEdit.payload); }); - test("deleting a spot's lineups leaves a teammate's new lineup from that spot", async () => { - const { owner, editor } = await createHarness(); - await apply( - owner, - "owner-client", - fanIn.map((row, index) => addOp(row, index)), - ); - - // The owner deletes every lineup into "site" while the editor, who has - // not seen that yet, places a new lineup into the same spot. - const deletes = await apply(owner, "owner-client", [ - deleteOp("delete-heaven", "from-heaven", 1), - deleteOp("delete-mid", "from-mid", 1), - ]); - const added = await apply(editor, "editor-client", [ - addOp(lineup("from-garage", { originId: "garage", landingId: "site" }), 2), - ]); - expect(deletes.map((result) => result.status)).toEqual([ - "applied", - "applied", - ]); - expect(added[0]).toMatchObject({ status: "applied" }); - - const rows = await pageLineups(owner); - expect(liveKeys(rows)).toEqual(["from-garage"]); - expect(byKey(rows).get("from-garage")!.payload.data.landing).toMatchObject( - { id: "site" }, - ); - }); - - test("a viewer reads lineups but cannot write them", async () => { + test("a viewer reads lineup groups but cannot write them", async () => { const { owner, viewer } = await createHarness(); - await apply(owner, "owner-client", [addOp(fanIn[0]!, 0)]); + await apply(owner, "owner-client", [addOp(siteGroup(), 0)]); await expect( - apply(viewer, "viewer-client", [addOp(fanIn[1]!, 1)]), + apply(viewer, "viewer-client", [addOp(lineup("viewer-lineup"), 1)]), ).rejects.toThrow("Forbidden"); await expect( apply(viewer, "viewer-client", [ patchOp( "viewer-renames", - lineup("from-heaven", { - originId: "heaven", - landingId: "site", - name: "Viewer", + siteGroup((lineups) => { + lineups.links[0]!.name = "Viewer"; }), 1, ), ]), ).rejects.toThrow("Forbidden"); expect((await pageLineups(viewer)).map((row) => row.publicId)).toEqual([ - "from-heaven", + siteKey, ]); }); }); -describe("a lineup row must be drawable on its own", () => { - test("a row missing its origin or landing, or keyed apart from its id, is refused", async () => { - const { owner } = await createHarness(); - const valid = lineupPayload("k"); - const withData = (id: string, data: Record) => ({ - ...addOp(lineup(id), 0), - payload: { ...valid, data: { ...valid.data, id, ...data } }, - }); - const withoutOrigin = withData("no-origin", { origin: undefined }); - delete (withoutOrigin.payload.data as Record).origin; - const withoutLanding = withData("no-landing", {}); - delete (withoutLanding.payload.data as Record).landing; - const originWithoutAgent = withData("no-agent", { - origin: { id: "o" }, - }); - const landingWithoutAbility = withData("no-ability", { - landing: { id: "l" }, - }); - const originWithoutId = withData("no-origin-id", { - origin: { agent: valid.data.origin.agent }, - }); - const mismatchedKey = { ...addOp(lineup("k"), 0), lineupPublicId: "other" }; - const graphKey = { ...addOp(lineup("k"), 0), lineupPublicId: "lineupLink:k" }; +describe("a lineup group row is checked whole", () => { + /// An add of the site group under [key] whose payload [edit] has changed. + function brokenAdd( + key: string, + edit: (payload: { + kind: string; + payloadVersion: number; + data: Record; + }) => void, + ) { + const payload = structuredClone(group(key, siteLineups()).payload) as { + kind: string; + payloadVersion: number; + data: Record; + }; + edit(payload); + return { ...addOp({ key, payload }, 0), opId: `add-${key}` }; + } - const results = await apply(owner, "owner-client", [ - withoutOrigin, - withoutLanding, - originWithoutAgent, - landingWithoutAbility, - originWithoutId, - mismatchedKey, - graphKey, - ]); - expect(results).toHaveLength(7); - for (const result of results) { - expect(result).toMatchObject({ - status: "failed", + test("each broken rule refuses the row with its code, and nothing is stored", async () => { + const { owner } = await createHarness(); + const cases: Array<{ + op: Record; + code: string; + }> = [ + { + op: brokenAdd("unknown-version", (payload) => { + payload.payloadVersion = 2; + }), + code: "INVALID_LINEUP_PAYLOAD_VERSION", + }, + { + op: brokenAdd("no-id", (payload) => { + delete payload.data.id; + }), code: "INVALID_LINEUP_PAYLOAD_DATA", - }); - } + }, + { + op: { ...brokenAdd("keyed", () => {}), lineupPublicId: "other-key" }, + code: "INVALID_LINEUP_PAYLOAD_DATA", + }, + ...(["origins", "landings", "links"] as const).flatMap((field) => [ + { + op: brokenAdd(`no-${field}`, (payload) => { + delete payload.data[field]; + }), + code: "INVALID_LINEUP_PAYLOAD_DATA", + }, + { + op: brokenAdd(`${field}-not-a-list`, (payload) => { + payload.data[field] = { [payload.data[field][0].id]: true }; + }), + code: "INVALID_LINEUP_PAYLOAD_DATA", + }, + { + op: brokenAdd(`${field}-entry-not-an-object`, (payload) => { + payload.data[field][0] = payload.data[field][0].id; + }), + code: "INVALID_LINEUP_PAYLOAD_DATA", + }, + { + op: brokenAdd(`${field}-entry-without-id`, (payload) => { + delete payload.data[field][0].id; + }), + code: "INVALID_LINEUP_PAYLOAD_DATA", + }, + { + op: brokenAdd(`${field}-entry-with-empty-id`, (payload) => { + payload.data[field][0].id = ""; + }), + code: "INVALID_LINEUP_PAYLOAD_DATA", + }, + { + op: brokenAdd(`${field}-repeat-an-id`, (payload) => { + payload.data[field][1].id = payload.data[field][0].id; + }), + code: "INVALID_LINEUP_PAYLOAD_DATA", + }, + ]), + { + op: brokenAdd("origin-without-agent", (payload) => { + delete payload.data.origins[0].agent; + }), + code: "INVALID_LINEUP_PAYLOAD_DATA", + }, + { + op: brokenAdd("landing-without-ability", (payload) => { + delete payload.data.landings[0].ability; + }), + code: "INVALID_LINEUP_PAYLOAD_DATA", + }, + { + op: brokenAdd("no-lineups", (payload) => { + payload.data.links = []; + }), + code: "INVALID_LINEUP_PAYLOAD_DATA", + }, + { + op: brokenAdd("link-to-missing-origin", (payload) => { + payload.data.links[0].originId = "nowhere"; + }), + code: "INVALID_LINEUP_PAYLOAD_DATA", + }, + { + op: brokenAdd("link-to-missing-landing", (payload) => { + payload.data.links[0].landingId = "nowhere"; + }), + code: "INVALID_LINEUP_PAYLOAD_DATA", + }, + { + op: brokenAdd("link-without-origin", (payload) => { + delete payload.data.links[0].originId; + }), + code: "INVALID_LINEUP_PAYLOAD_DATA", + }, + { + op: brokenAdd("link-without-landing", (payload) => { + delete payload.data.links[0].landingId; + }), + code: "INVALID_LINEUP_PAYLOAD_DATA", + }, + { + // A link may not name a landing as its origin. + op: brokenAdd("link-ends-swapped", (payload) => { + payload.data.links[0].originId = "site"; + payload.data.links[0].landingId = "heaven"; + }), + code: "INVALID_LINEUP_PAYLOAD_DATA", + }, + ]; + + const results = await apply( + owner, + "owner-client", + cases.map((entry) => entry.op), + ); + expect(results).toEqual( + cases.map((entry) => + expect.objectContaining({ + opId: entry.op.opId, + status: "failed", + code: entry.code, + }), + ), + ); expect(await pageLineups(owner)).toEqual([]); }); - test("a patch that drops an end is refused and leaves the row as it was", async () => { + test("a spot no lineup uses is kept", async () => { + const { owner } = await createHarness(); + const row = siteGroup((lineups) => { + lineups.origins.push({ id: "unused-origin", agentType: "jett" }); + lineups.landings.push({ id: "unused-landing" }); + }); + const results = await apply(owner, "owner-client", [addOp(row, 0)]); + expect(results[0]).toMatchObject({ status: "applied" }); + expect((await pageLineups(owner))[0]!.payload).toEqual(row.payload); + }); + + test("a patch that breaks the group is refused and leaves the row as it was", async () => { const { owner } = await createHarness(); - await apply(owner, "owner-client", [addOp(fanIn[0]!, 0)]); - const { landing: _landing, ...withoutLanding } = fanIn[0]!.payload.data; + await apply(owner, "owner-client", [addOp(siteGroup(), 0)]); + // The landing two lineups aim at is gone, but they still name it. + const broken = siteGroup((lineups) => { + lineups.landings = lineups.landings.filter( + (landing) => landing.id !== "site", + ); + }); const results = await apply(owner, "owner-client", [ - { - ...patchOp("drop-landing", fanIn[0]!, 1), - payload: { ...fanIn[0]!.payload, data: withoutLanding }, - }, + patchOp("drop-site", broken, 1), ]); expect(results[0]).toMatchObject({ status: "failed", @@ -643,10 +646,10 @@ describe("a lineup row must be drawable on its own", () => { }); const row = (await pageLineups(owner))[0]!; expect(row).toMatchObject({ revision: 1 }); - expect(row.payload).toEqual(fanIn[0]!.payload); + expect(row.payload).toEqual(siteGroup().payload); }); - test("a row of an old graph kind is refused by the handler, the legacy group by the contract", async () => { + test("the graph kinds of protocol 4 are refused by the handler; lineup and lineupGroup by the contract", async () => { const { owner } = await createHarness(); // Protocol 4's graph rows get past argument validation so an old client // reaches the protocol gate; on protocol 5 the handler refuses each op. @@ -675,26 +678,41 @@ describe("a lineup row must be drawable on its own", () => { code: "INVALID_LINEUP_PAYLOAD_KIND", }); } - // The group of protocol 3 and earlier is still refused whole. - await expect( - apply(owner, "old-client", [ - { - opId: "add-lineupGroup", - type: "lineup.add", - lineupPublicId: "group", - pagePublicId, - payload: { - kind: "lineupGroup", - payloadVersion: 1, - data: { id: "group", items: [{ id: "item" }] }, - }, - sortIndex: 0, + // The group of protocol 3 and earlier, and the one-row-per-lineup shape + // this branch tried before groups, are refused whole: no client that + // could send them speaks protocol 5. + const retired = [ + { + kind: "lineupGroup", + payloadVersion: 1, + data: { id: "group", items: [{ id: "item" }] }, + }, + { + kind: "lineup", + payloadVersion: 1, + data: { + id: "group", + origin: { id: "o", agent: { type: "sova" } }, + landing: { id: "l", ability: {} }, }, - ]), - ).rejects.toThrow(/Validator error|ArgumentValidationError/); + }, + ]; + for (const payload of retired) { + await expect( + apply(owner, "old-client", [ + { + opId: `add-${payload.kind}`, + type: "lineup.add", + lineupPublicId: "group", + pagePublicId, + payload, + sortIndex: 0, + }, + ]), + ).rejects.toThrow(/Validator error|ArgumentValidationError/); + } expect(await pageLineups(owner)).toEqual([]); }); - }); describe("clients on protocol 4", () => { @@ -796,7 +814,7 @@ describe("clients on protocol 4", () => { }); describe("lineup row keys belong to their strategy and page", () => { - test("the same lineup ids in two strategies are separate rows", async () => { + test("the same group ids in two strategies are separate rows", async () => { const { owner } = await createHarness(); const copyId = "local-duplicate"; const copyPage = "local-duplicate-page"; @@ -818,13 +836,10 @@ describe("lineup row keys belong to their strategy and page", () => { const results = await apply( owner, `upload-${strategy}`, - fanIn.map((row, index) => addOp(row, index, page)), + [addOp(siteGroup(), 0, page)], strategy, ); - expect(results.map((result) => result.status)).toEqual([ - "applied", - "applied", - ]); + expect(results[0]).toMatchObject({ status: "applied" }); } // Editing one copy leaves the other untouched. await apply( @@ -834,10 +849,8 @@ describe("lineup row keys belong to their strategy and page", () => { { ...patchOp( "rename-in-copy", - lineup("from-heaven", { - originId: "heaven", - landingId: "site", - name: "Copy name", + siteGroup((lineups) => { + lineups.links[0]!.name = "Copy name"; }), 1, ), @@ -846,10 +859,16 @@ describe("lineup row keys belong to their strategy and page", () => { ], copyId, ); - const original = byKey(await pageLineups(owner)); - const copy = byKey(await pageLineups(owner, copyId, copyPage)); - expect(original.get("from-heaven")!.payload.data.name).toBe("From heaven"); - expect(copy.get("from-heaven")!.payload.data.name).toBe("Copy name"); + const firstLinkName = (row: LineupRow | undefined) => + (row!.payload.data as { links: Array<{ name: string }> }).links[0]!.name; + expect(firstLinkName(byKey(await pageLineups(owner)).get(siteKey))).toBe( + "From heaven", + ); + expect( + firstLinkName( + byKey(await pageLineups(owner, copyId, copyPage)).get(siteKey), + ), + ).toBe("Copy name"); }); test("a lineup row is never moved to another page by a patch", async () => { @@ -883,19 +902,19 @@ describe("lineup row keys belong to their strategy and page", () => { expect(await pageLineups(owner, strategyPublicId, secondPage)).toEqual([]); }); - test("the same lineup added twice with a different order is one add", async () => { + test("the same group added twice with a different order is one add", async () => { const { owner, editor } = await createHarness(); - const first = await apply(owner, "owner-client", [addOp(fanIn[0]!, 3)]); - const second = await apply(editor, "editor-client", [addOp(fanIn[0]!, 7)]); + const first = await apply(owner, "owner-client", [addOp(siteGroup(), 3)]); + const second = await apply(editor, "editor-client", [ + addOp(siteGroup(), 7), + ]); expect(first[0]).toMatchObject({ status: "applied" }); expect(second[0]).toMatchObject({ status: "noop" }); const conflicting = await apply(editor, "editor-client", [ { ...addOp( - lineup("from-heaven", { - originId: "heaven", - landingId: "site", - agentType: "jett", + siteGroup((lineups) => { + lineups.origins[0]!.agentType = "jett"; }), 7, ), @@ -910,16 +929,17 @@ describe("lineup row keys belong to their strategy and page", () => { }); describe("lineup images", () => { - test("a lineup's images are referenced while it lives and released when it is deleted", async () => { + test("a group references every image across its lineups while it lives, and releases them when it is deleted", async () => { const { t, owner } = await createHarness(); await t.run(markAssetReferencesReady); + const shots = ["shot-1", "shot-2", "shot-3"]; await t.run(async (ctx) => { const strategy = await ctx.db .query("strategies") .withIndex("by_publicId", (q) => q.eq("publicId", strategyPublicId)) .unique(); const now = Date.now(); - for (const publicId of ["shot-1", "shot-2"]) { + for (const publicId of shots) { await ctx.db.insert("imageAssets", { publicId, provider: "r2", @@ -927,58 +947,67 @@ describe("lineup images", () => { objectKey: `tests/${publicId}.png`, uploadStatus: "active", fileExtension: ".png", + mimeType: "image/png", createdAt: now, updatedAt: now, }); } }); - const row = lineup("k", { - images: [ - { id: "shot-1", fileExtension: ".png" }, - { id: "shot-2", fileExtension: ".png" }, - ], - }); - await apply(owner, "owner-client", [addOp(row, 0)]); + // Two lineups of the group show images, one of them shared by both. + const withImages = (secondLinkShots: string[]) => + siteGroup((lineups) => { + lineups.links[0]!.images = [ + { id: "shot-1", fileExtension: ".png" }, + { id: "shot-2", fileExtension: ".png" }, + ]; + lineups.links[2]!.images = secondLinkShots.map((id) => ({ + id, + fileExtension: ".png", + })); + }); + await apply(owner, "owner-client", [ + addOp(withImages(["shot-2", "shot-3"]), 0), + ]); const snapshot = (await owner.query(getPageSnapshot, { clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, strategyPublicId, pagePublicId, })) as { assets: Array<{ publicId: string }> }; - expect(snapshot.assets.map((asset) => asset.publicId).sort()).toEqual([ - "shot-1", - "shot-2", - ]); - const references = await t.run(async (ctx) => - (await ctx.db.query("assetReferences").collect()).filter( - (reference) => reference.lineupId !== undefined, - ), + expect(snapshot.assets.map((asset) => asset.publicId).sort()).toEqual( + shots, ); - expect( - references.map((reference) => reference.assetPublicId).sort(), - ).toEqual(["shot-1", "shot-2"]); + // One reference per image the row shows, however many lineups show it. + const lineupReferences = async () => + ( + await t.run(async (ctx) => await ctx.db.query("assetReferences").collect()) + ) + .filter((reference) => reference.lineupId !== undefined) + .map((reference) => reference.assetPublicId) + .sort(); + expect(await lineupReferences()).toEqual(shots); const asked = { strategyPublicId, - assetPublicIds: ["shot-1", "shot-2", "never-shown"], + assetPublicIds: [...shots, "never-shown"], }; - expect(await owner.query(listReferencedAssetIds, asked)).toEqual([ - "shot-1", - "shot-2", - ]); + expect(await owner.query(listReferencedAssetIds, asked)).toEqual(shots); - // Dropping one image from the lineup releases only that one. + // Dropping an image one lineup still shows keeps it; dropping the last + // lineup showing an image releases only that one. await apply(owner, "owner-client", [ - patchOp( - "drop-shot-2", - lineup("k", { images: [{ id: "shot-1", fileExtension: ".png" }] }), - 1, - ), + patchOp("drop-shared-shot", withImages(["shot-3"]), 1), + ]); + expect(await owner.query(listReferencedAssetIds, asked)).toEqual(shots); + await apply(owner, "owner-client", [ + patchOp("drop-shot-3", withImages([]), 2), ]); expect(await owner.query(listReferencedAssetIds, asked)).toEqual([ "shot-1", + "shot-2", ]); + expect(await lineupReferences()).toEqual(["shot-1", "shot-2"]); - await apply(owner, "owner-client", [deleteOp("delete-k", "k", 2)]); + await apply(owner, "owner-client", [deleteOp("delete-site", siteKey, 3)]); expect(await owner.query(listReferencedAssetIds, asked)).toEqual([]); }); }); @@ -987,22 +1016,40 @@ describe("agent summary", () => { test("an origin shared by several lineups counts once, distinct origins count each", async () => { const { t, owner } = await createHarness(); await apply(owner, "owner-client", [ - // One astra origin shared by two lineups. - addOp(lineup("astra-a", { originId: "astra", agentType: "astra" }), 0), - addOp(lineup("astra-b", { originId: "astra", agentType: "astra" }), 1), - // Two viper origins. - addOp(lineup("viper-a", { originId: "viper-1", agentType: "viper" }), 2), - addOp(lineup("viper-b", { originId: "viper-2", agentType: "viper" }), 3), + // One astra origin, thrown to two landings. + addOp( + group("astra", { + origins: [{ id: "astra-origin", agentType: "astra" }], + landings: [{ id: "a-site" }, { id: "b-site" }], + links: [ + { id: "astra-a", originId: "astra-origin", landingId: "a-site" }, + { id: "astra-b", originId: "astra-origin", landingId: "b-site" }, + ], + }), + 0, + ), + // Two viper origins into one landing. + addOp( + group("viper", { + origins: [ + { id: "viper-1", agentType: "viper" }, + { id: "viper-2", agentType: "viper" }, + ], + landings: [{ id: "wall" }], + links: [ + { id: "viper-a", originId: "viper-1", landingId: "wall" }, + { id: "viper-b", originId: "viper-2", landingId: "wall" }, + ], + }), + 1, + ), ]); // Counted per lineup, astra would tie viper and sort first by name; // counted per origin, viper (2) leads astra (1). expect(await agentSummary(t)).toEqual(["viper", "astra"]); - // Deleting every lineup from an origin drops its agent. - await apply(owner, "owner-client", [ - deleteOp("delete-astra-a", "astra-a", 1), - deleteOp("delete-astra-b", "astra-b", 1), - ]); + // Deleting the group of an origin drops its agent. + await apply(owner, "owner-client", [deleteOp("delete-astra", "astra", 1)]); expect(await agentSummary(t)).toEqual(["viper"]); }); @@ -1070,78 +1117,93 @@ describe("agent summary", () => { expect(await agentSummary(t)).toEqual(["astra", "kayo", "sova", "viper"]); }); - test("each live lineup keeps one agent row in step with it, and the summary follows them", async () => { + test("each live group keeps one agent row per origin in step with it, and the summary follows them", async () => { const { t, owner } = await createHarness(); const secondPage = "lineups-page-2"; await addSecondPage(owner, secondPage); - const astra = (notes = "") => - lineup("astra-a", { originId: "astra", agentType: "astra", notes }); - const viper = (agentType: string) => - lineup("viper-a", { originId: "viper-1", agentType }); const kayo = lineup("kayo-a", { originId: "kayo", agentType: "kayo" }); + const rawRows = async () => + await t.run(async (ctx) => await ctx.db.query("lineupAgents").collect()); - // Adding a lineup adds its row. + // Adding a group adds a row for each of its origins. await apply(owner, "owner-client", [ - addOp(astra(), 0), - addOp(viper("viper"), 1), + addOp(siteGroup(), 0), addOp(kayo, 0, secondPage), ]); expect(await lineupAgentRows(t)).toEqual([ - { lineup: "astra-a", page: pagePublicId, originId: "astra", agentType: "astra" }, { lineup: "kayo-a", page: secondPage, originId: "kayo", agentType: "kayo" }, - { lineup: "viper-a", page: pagePublicId, originId: "viper-1", agentType: "viper" }, + { lineup: siteKey, page: pagePublicId, originId: "heaven", agentType: "brimstone" }, + { lineup: siteKey, page: pagePublicId, originId: "mid", agentType: "viper" }, ]); - expect(await agentSummary(t)).toEqual(["astra", "kayo", "viper"]); - - // A patch that changes the origin's agent updates its row; one that - // leaves the agent alone leaves it as it was. - const astraRowBefore = await t.run(async (ctx) => - (await ctx.db.query("lineupAgents").collect()).find( - (row) => row.originId === "astra", - ), - ); - const patched = await apply(owner, "owner-client", [ - patchOp("viper-to-sova", viper("sova"), 1), - patchOp("astra-notes", astra("Run and throw"), 1), - ]); - expect(patched.map((result) => result.status)).toEqual([ - "applied", - "applied", - ]); - expect(await lineupAgentRows(t)).toEqual([ - { lineup: "astra-a", page: pagePublicId, originId: "astra", agentType: "astra" }, - { lineup: "kayo-a", page: secondPage, originId: "kayo", agentType: "kayo" }, - { lineup: "viper-a", page: pagePublicId, originId: "viper-1", agentType: "sova" }, + expect(await agentSummary(t)).toEqual(["brimstone", "kayo", "viper"]); + + // A patch that changes one origin's agent updates that row alone; the + // other origin's row is left exactly as it was. + const before = await rawRows(); + const heavenRow = before.find((row) => row.originId === "heaven")!; + const midRow = before.find((row) => row.originId === "mid")!; + const midToSova = siteGroup((lineups) => { + lineups.origins[1]!.agentType = "sova"; + }); + expect( + (await apply(owner, "owner-client", [patchOp("mid-to-sova", midToSova, 1)]))[0], + ).toMatchObject({ status: "applied" }); + let after = await rawRows(); + expect(after.find((row) => row.originId === "heaven")).toEqual(heavenRow); + expect(after.find((row) => row.originId === "mid")).toEqual({ + ...midRow, + agentType: "sova", + }); + expect(await agentSummary(t)).toEqual(["brimstone", "kayo", "sova"]); + + // A lineup from a new origin adds its row; dropping an origin (and its + // lineup) deletes its row, leaving the rest. + const reshaped = siteGroup((lineups) => { + lineups.origins[1]!.agentType = "sova"; + lineups.origins.push({ id: "garage", agentType: "astra" }); + lineups.links.push({ id: "from-garage", originId: "garage", landingId: "site" }); + }); + await apply(owner, "owner-client", [patchOp("add-garage", reshaped, 2)]); + expect( + (await lineupAgentRows(t)).filter((row) => row.lineup === siteKey), + ).toEqual([ + { lineup: siteKey, page: pagePublicId, originId: "garage", agentType: "astra" }, + { lineup: siteKey, page: pagePublicId, originId: "heaven", agentType: "brimstone" }, + { lineup: siteKey, page: pagePublicId, originId: "mid", agentType: "sova" }, ]); + const withoutMid = siteGroup((lineups) => { + lineups.origins = lineups.origins.filter((origin) => origin.id !== "mid"); + lineups.origins.push({ id: "garage", agentType: "astra" }); + lineups.links = lineups.links.filter((link) => link.originId !== "mid"); + lineups.links.push({ id: "from-garage", originId: "garage", landingId: "site" }); + }); + await apply(owner, "owner-client", [patchOp("drop-mid", withoutMid, 3)]); + after = await rawRows(); + expect(after.find((row) => row.originId === "heaven")).toEqual(heavenRow); expect( - await t.run(async (ctx) => - (await ctx.db.query("lineupAgents").collect()).find( - (row) => row.originId === "astra", - ), - ), - ).toEqual(astraRowBefore); - expect(await agentSummary(t)).toEqual(["astra", "kayo", "sova"]); + (await lineupAgentRows(t)).filter((row) => row.lineup === siteKey), + ).toEqual([ + { lineup: siteKey, page: pagePublicId, originId: "garage", agentType: "astra" }, + { lineup: siteKey, page: pagePublicId, originId: "heaven", agentType: "brimstone" }, + ]); + expect(await agentSummary(t)).toEqual(["astra", "brimstone", "kayo"]); - // Deleting a lineup removes its row; restoring it brings the row back. - await apply(owner, "owner-client", [deleteOp("delete-astra", "astra-a", 2)]); + // Deleting a group removes all its rows; restoring it brings them back. + await apply(owner, "owner-client", [deleteOp("delete-site", siteKey, 4)]); expect((await lineupAgentRows(t)).map((row) => row.lineup)).toEqual([ "kayo-a", - "viper-a", ]); - expect(await agentSummary(t)).toEqual(["kayo", "sova"]); + expect(await agentSummary(t)).toEqual(["kayo"]); const restored = await apply(owner, "owner-client", [ - { ...addOp(astra(), 0), opId: "restore-astra", expectedLineupRevision: 3 }, + { ...addOp(withoutMid, 0), opId: "restore-site", expectedLineupRevision: 5 }, ]); - expect(restored[0]).toMatchObject({ status: "applied", appliedRevision: 4 }); - expect(await lineupAgentRows(t)).toContainEqual({ - lineup: "astra-a", - page: pagePublicId, - originId: "astra", - agentType: "astra", - }); - expect(await agentSummary(t)).toEqual(["astra", "kayo", "sova"]); + expect(restored[0]).toMatchObject({ status: "applied", appliedRevision: 6 }); + expect( + (await lineupAgentRows(t)).map((row) => `${row.lineup}:${row.originId}`), + ).toEqual(["kayo-a:kayo", `${siteKey}:garage`, `${siteKey}:heaven`]); + expect(await agentSummary(t)).toEqual(["astra", "brimstone", "kayo"]); - // A page in the trash keeps its lineups' rows for a restore, and the + // A page in the trash keeps its groups' rows for a restore, and the // summary leaves them out until the page comes back. const trashed = await apply(owner, "owner-client", [ { @@ -1153,17 +1215,17 @@ describe("agent summary", () => { ]); expect(trashed[0]).toMatchObject({ status: "applied" }); expect((await lineupAgentRows(t)).map((row) => row.lineup)).toEqual([ - "astra-a", "kayo-a", - "viper-a", + siteKey, + siteKey, ]); - expect(await agentSummary(t)).toEqual(["astra", "sova"]); + expect(await agentSummary(t)).toEqual(["astra", "brimstone"]); await owner.mutation(restorePage, { clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, strategyPublicId, pagePublicId: secondPage, }); - expect(await agentSummary(t)).toEqual(["astra", "kayo", "sova"]); + expect(await agentSummary(t)).toEqual(["astra", "brimstone", "kayo"]); }); test("purging leaves no agent row behind, even one a tombstone kept", async () => { @@ -1171,10 +1233,10 @@ describe("agent summary", () => { const { t, owner } = await createHarness(); await t.run(markAssetReferencesReady); await apply(owner, "owner-client", [ - addOp(lineup("gone", { agentType: "astra" }), 0), + addOp(siteGroup(), 0), addOp(lineup("kept", { agentType: "viper" }), 1), ]); - await apply(owner, "owner-client", [deleteOp("delete-gone", "gone", 1)]); + await apply(owner, "owner-client", [deleteOp("delete-site", siteKey, 1)]); expect((await lineupAgentRows(t)).map((row) => row.lineup)).toEqual([ "kept", ]); @@ -1182,14 +1244,14 @@ describe("agent summary", () => { // table existed) goes with the tombstone. await t.run(async (ctx) => { const gone = (await ctx.db.query("lineups").collect()).find( - (row) => row.publicId === "gone", + (row) => row.publicId === siteKey, )!; await ctx.db.insert("lineupAgents", { strategyId: gone.strategyId, pageId: gone.pageId, lineupId: gone._id, - originId: "gone-origin", - agentType: "astra", + originId: "heaven", + agentType: "brimstone", }); }); @@ -1209,12 +1271,11 @@ describe("agent summary", () => { ]); }); - test("a duplicate's lineups get agent rows of their own", async () => { + test("a duplicate's groups get agent rows of their own, one per origin", async () => { const { t, owner } = await createHarness(); await apply(owner, "owner-client", [ - addOp(lineup("astra-a", { originId: "astra", agentType: "astra" }), 0), - addOp(lineup("astra-b", { originId: "astra", agentType: "astra" }), 1), - addOp(lineup("viper-a", { originId: "viper", agentType: "viper" }), 2), + addOp(siteGroup(), 0), + addOp(lineup("astra-a", { originId: "astra", agentType: "astra" }), 1), ]); const sourceRows = await lineupAgentRows(t); @@ -1225,6 +1286,7 @@ describe("agent summary", () => { name: "Lineups (Copy)", }); + type Origin = { id: string; agent: { type: string } }; const copy = (await owner.query(getFullSnapshot, { clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, strategyPublicId: "lineups-copy", @@ -1235,19 +1297,19 @@ describe("agent summary", () => { const copyRows = await lineupAgentRows(t, "lineups-copy"); expect(copyRows).toEqual( copy.lineups - .map((row) => { - const origin = row.payload.data.origin as { - id: string; - agent: { type: string }; - }; - return { + .flatMap((row) => + (row.payload.data.origins as Origin[]).map((origin) => ({ lineup: row.publicId, page: row.pagePublicId, originId: origin.id, agentType: origin.agent.type, - }; - }) - .sort((a, b) => a.lineup.localeCompare(b.lineup)), + })), + ) + .sort( + (a, b) => + a.lineup.localeCompare(b.lineup) || + a.originId.localeCompare(b.originId), + ), ); expect(copyRows).toHaveLength(3); expect(copyRows.map((row) => row.page)).toEqual( @@ -1255,13 +1317,23 @@ describe("agent summary", () => { ); // The source keeps its rows untouched. expect(await lineupAgentRows(t)).toEqual(sourceRows); - expect(await agentSummary(t, "lineups-copy")).toEqual(["astra", "viper"]); + expect(await agentSummary(t, "lineups-copy")).toEqual([ + "astra", + "brimstone", + "viper", + ]); // Editing the copy moves only the copy's rows. - const viperCopy = copyRows.find((row) => row.agentType === "viper")!; - const viperCopyLanding = copy.lineups.find( - (row) => row.publicId === viperCopy.lineup, - )!.payload.data.landing as { id: string }; + const copiedSite = copy.lineups.find( + (row) => (row.payload.data.origins as Origin[]).length === 2, + )!; + const data = structuredClone(copiedSite.payload.data) as { + origins: Origin[]; + }; + const viperOrigin = data.origins.find( + (origin) => origin.agent.type === "viper", + )!; + viperOrigin.agent.type = "jett"; const edited = await apply( owner, "copy-client", @@ -1269,13 +1341,9 @@ describe("agent summary", () => { { opId: "copy-viper-to-jett", type: "lineup.patch", - lineupPublicId: viperCopy.lineup, - pagePublicId: viperCopy.page, - payload: lineupPayload(viperCopy.lineup, { - originId: viperCopy.originId, - landingId: viperCopyLanding.id, - agentType: "jett", - }), + lineupPublicId: copiedSite.publicId, + pagePublicId: copiedSite.pagePublicId, + payload: { ...copiedSite.payload, data }, expectedLineupRevision: 1, }, ], @@ -1284,36 +1352,36 @@ describe("agent summary", () => { expect(edited[0]).toMatchObject({ status: "applied" }); expect( (await lineupAgentRows(t, "lineups-copy")).find( - (row) => row.lineup === viperCopy.lineup, + (row) => row.originId === viperOrigin.id, ), - ).toEqual({ ...viperCopy, agentType: "jett" }); - expect(await agentSummary(t, "lineups-copy")).toEqual(["astra", "jett"]); + ).toMatchObject({ lineup: copiedSite.publicId, agentType: "jett" }); + expect(await agentSummary(t, "lineups-copy")).toEqual([ + "astra", + "brimstone", + "jett", + ]); expect(await lineupAgentRows(t)).toEqual(sourceRows); - expect(await agentSummary(t)).toEqual(["astra", "viper"]); + expect(await agentSummary(t)).toEqual(["astra", "brimstone", "viper"]); }); }); describe("a resent lineup op", () => { test("reports the revision it landed at, so a successor cannot overwrite a teammate's later edit", async () => { const { owner, editor } = await createHarness(); - await apply(owner, "owner-client", [addOp(fanIn[0]!, 0)]); + await apply(owner, "owner-client", [addOp(siteGroup(), 0)]); const mine = patchOp( "owner-renames", - lineup("from-heaven", { - originId: "heaven", - landingId: "site", - name: "Owner name", + siteGroup((lineups) => { + lineups.links[0]!.name = "Owner name"; }), 1, ); const first = await apply(owner, "owner-client", [mine]); expect(first[0]).toMatchObject({ status: "applied", appliedRevision: 2 }); - // A teammate edits the same lineup after it. - const theirs = lineup("from-heaven", { - originId: "heaven", - landingId: "site", - name: "Editor name", + // A teammate edits the same group after it. + const theirs = siteGroup((lineups) => { + lineups.links[0]!.name = "Editor name"; }); const teammate = await apply(editor, "editor-client", [ patchOp("editor-renames", theirs, 2), @@ -1326,11 +1394,9 @@ describe("a resent lineup op", () => { mine, patchOp( "owner-notes", - lineup("from-heaven", { - originId: "heaven", - landingId: "site", - name: "Owner name", - notes: "Owner notes", + siteGroup((lineups) => { + lineups.links[0]!.name = "Owner name"; + lineups.links[0]!.notes = "Owner notes"; }), 2, ), @@ -1345,10 +1411,49 @@ describe("a resent lineup op", () => { reason: "revision_mismatch", current: { type: "lineup", revision: 3, value: theirs.payload }, }); - const row = byKey(await pageLineups(owner)).get("from-heaven")!; + const row = byKey(await pageLineups(owner)).get(siteKey)!; expect(row.revision).toBe(3); expect(row.payload).toEqual(theirs.payload); }); + + test("whose event recorded no revision replays with none, not the row's latest", async () => { + const { t, owner, editor } = await createHarness(); + const mine = addOp(siteGroup(), 0); + const first = await apply(owner, "owner-client", [mine]); + expect(first[0]).toMatchObject({ status: "applied", appliedRevision: 1 }); + // An event written without the revision its op landed at. + await t.run(async (ctx) => { + const event = (await ctx.db.query("operationEvents").collect()).find( + (row) => row.opId === mine.opId, + )!; + await ctx.db.patch(event._id, { appliedRevision: undefined }); + }); + // A teammate moves the row on. + const theirs = siteGroup((lineups) => { + lineups.links[0]!.name = "Editor name"; + }); + await apply(editor, "editor-client", [patchOp("editor-renames", theirs, 1)]); + + // The replay names no revision, so the client cannot rebase a successor + // onto the teammate's edit; the successor, still on 1, meets the usual + // revision check. + const resent = await apply(owner, "owner-client", [ + mine, + patchOp( + "owner-renames", + siteGroup((lineups) => { + lineups.links[0]!.name = "Owner name"; + }), + 1, + ), + ]); + expect(resent[0]).toEqual({ opId: mine.opId, status: "noop" }); + expect(resent[1]).toMatchObject({ + status: "rejected", + reason: "revision_mismatch", + current: { type: "lineup", revision: 2, value: theirs.payload }, + }); + }); }); function elementPayload( @@ -1383,8 +1488,8 @@ function elementAdd( }; } -/// Writes a live lineup straight to the table, bypassing applyBatch and so -/// the agent summary refresh. +/// Writes a live group of one lineup straight to the table, bypassing +/// applyBatch and so the agent summary refresh. async function seedLineupRow(t: RootHarness, id: string, agentType: string) { await t.run(async (ctx) => { const strategy = await ctx.db @@ -1400,9 +1505,9 @@ async function seedLineupRow(t: RootHarness, id: string, agentType: string) { publicId: id, strategyId: strategy!._id, pageId: page!._id, - payloadKind: "lineup", + payloadKind: "lineups", payloadVersion: 1, - payload: lineupPayload(id, { agentType }), + payload: oneLineupPayload(id, { agentType }), sortIndex: 0, revision: 1, deleted: false, diff --git a/convex/maintenance.ts b/convex/maintenance.ts index f3306540..b75a04e7 100644 --- a/convex/maintenance.ts +++ b/convex/maintenance.ts @@ -21,7 +21,7 @@ import { } from "./images"; import { refreshStrategyAgentSummary, - syncLineupAgent, + syncLineupAgents, } from "./lib/strategyAgentSummary"; import { PAGE_TRASH_RETENTION_MS } from "./lib/entities"; @@ -114,7 +114,7 @@ async function purgeContentRows( break; } if ("lineupId" in row) { - await syncLineupAgent(ctx, row.lineupId, null); + await syncLineupAgents(ctx, row.lineupId, null); await ctx.db.delete(row.lineupId); } else { await ctx.db.delete(row.elementId); diff --git a/convex/mediaLimits.test.ts b/convex/mediaLimits.test.ts index a86aada9..aa2c2542 100644 --- a/convex/mediaLimits.test.ts +++ b/convex/mediaLimits.test.ts @@ -21,7 +21,7 @@ import { modules } from "./test.setup"; import { insertElement, insertLineup, - lineupPayload, + oneLineupPayload, } from "./testContent.helpers"; const ensureCurrentUser = makeFunctionReference<"mutation">( @@ -368,7 +368,7 @@ describe("media cleanup stays within transaction limits", () => { type: "lineup.add", lineupPublicId: "link", pagePublicId, - payload: lineupPayload("link", { images: [{ id: "old-image" }] }), + payload: oneLineupPayload("link", { images: [{ id: "old-image" }] }), sortIndex: 0, }, ], @@ -416,7 +416,7 @@ describe("asset references follow their content", () => { type: "lineup.add", lineupPublicId: "k", pagePublicId, - payload: lineupPayload("k", { images: [{ id: "a" }, { id: "b" }] }), + payload: oneLineupPayload("k", { images: [{ id: "a" }, { id: "b" }] }), sortIndex: 0, }, ], @@ -446,7 +446,7 @@ describe("asset references follow their content", () => { type: "lineup.patch", lineupPublicId: "k", pagePublicId, - payload: lineupPayload("k", { images: [{ id: "a" }] }), + payload: oneLineupPayload("k", { images: [{ id: "a" }] }), expectedLineupRevision: 1, }, ], @@ -512,9 +512,9 @@ describe("asset references follow their content", () => { publicId: `before-${index}`, strategyId, pageId, - payloadKind: "lineup", + payloadKind: "lineups", payloadVersion: 1, - payload: lineupPayload(`before-${index}`, { + payload: oneLineupPayload(`before-${index}`, { images: [{ id: `link-image-${index}` }], }), sortIndex: index, @@ -617,9 +617,9 @@ describe("before the reference backfill", () => { publicId: "live", strategyId, pageId, - payloadKind: "lineup", + payloadKind: "lineups", payloadVersion: 1, - payload: lineupPayload("live", { images: [{ id: "lineup-shown" }] }), + payload: oneLineupPayload("live", { images: [{ id: "lineup-shown" }] }), sortIndex: 0, revision: 1, deleted: false, @@ -697,9 +697,9 @@ describe("the backfill and its gate", () => { publicId: linkId, strategyId, pageId, - payloadKind: "lineup", + payloadKind: "lineups", payloadVersion: 1, - payload: lineupPayload(linkId, { + payload: oneLineupPayload(linkId, { images: Array.from({ length: images }, (_, index) => ({ id: `${linkId}-${index}`, })), @@ -958,9 +958,9 @@ describe("purges stay within transaction limits", () => { publicId: `many-${lineup}`, strategyId, pageId, - payloadKind: "lineup", + payloadKind: "lineups", payloadVersion: 1, - payload: lineupPayload(`many-${lineup}`, { + payload: oneLineupPayload(`many-${lineup}`, { images: Array.from({ length: 850 }, (_, image) => ({ id: `image-${lineup}-${image}`, })), @@ -1203,9 +1203,9 @@ describe("duplicate stays within transaction limits", () => { publicId: `big-${index}`, strategyId, pageId, - payloadKind: "lineup", + payloadKind: "lineups", payloadVersion: 1, - payload: lineupPayload(`big-${index}`, { + payload: oneLineupPayload(`big-${index}`, { agentType: agentTypes[index % agentTypes.length], notes: largeText, }), @@ -1255,7 +1255,7 @@ describe("duplicate stays within transaction limits", () => { type: "lineup.patch", lineupPublicId: "big-0", pagePublicId, - payload: lineupPayload("big-0", { + payload: oneLineupPayload("big-0", { agentType: "jett", notes: largeText, }), @@ -1277,9 +1277,9 @@ describe("duplicate stays within transaction limits", () => { publicId: "gallery", strategyId, pageId, - payloadKind: "lineup", + payloadKind: "lineups", payloadVersion: 1, - payload: lineupPayload("gallery", { + payload: oneLineupPayload("gallery", { images: Array.from({ length: count }, (_, index) => ({ id: `gallery-${index}`, })), diff --git a/convex/ops.ts b/convex/ops.ts index f6b02808..246956e8 100644 --- a/convex/ops.ts +++ b/convex/ops.ts @@ -4,7 +4,7 @@ import type { Doc, Id } from "./_generated/dataModel"; import { assertCallerIsAccount, assertStrategyRole } from "./lib/auth"; import { refreshStrategyAgentSummary, - syncLineupAgent, + syncLineupAgents, } from "./lib/strategyAgentSummary"; import { expectAssets, @@ -38,6 +38,7 @@ import { cloudProtocolArgs, } from "./lib/cloudProtocol"; import { valuesEqual } from "./lib/canonicalValues"; +import { LINEUPS_PAYLOAD_VERSION } from "./lib/payloadValidators"; import { errorWithCode, invalidPayloadError, @@ -342,9 +343,12 @@ function invalidLineupData(message: string) { return errorWithCode("INVALID_LINEUP_PAYLOAD_DATA", message); } -/// Checks one lineup row before it is stored. A row is one whole lineup, -/// keyed by its id, holding its origin and landing: every row can be drawn -/// on its own, whatever other rows exist. +/// Checks one lineup group row before it is stored: a group keyed by its +/// id, whose origins, landings and links each have an id unique among their +/// kind, each origin placing an agent and each landing an ability, with at +/// least one link, and every link joining an origin and a landing the row +/// holds. A row that breaks any of these is refused whole, never stored. +/// A spot no link uses is allowed. function assertLineupPayload( payload: unknown, lineupPublicId: string, @@ -355,13 +359,13 @@ function assertLineupPayload( // Argument validation lets the graph rows of protocol 4 through, so an old // client reaches the protocol gate (see lineupOpPayloadValidator); none is // ever stored. - if (payload.kind !== "lineup") { + if (payload.kind !== "lineups") { throw errorWithCode( "INVALID_LINEUP_PAYLOAD_KIND", "Invalid lineup payload kind", ); } - if (typeof payload.payloadVersion !== "number") { + if (payload.payloadVersion !== LINEUPS_PAYLOAD_VERSION) { throw errorWithCode( "INVALID_LINEUP_PAYLOAD_VERSION", "Invalid lineup payload version", @@ -375,28 +379,67 @@ function assertLineupPayload( } const data = payload.data; if (typeof data.id !== "string" || data.id.length === 0) { - throw invalidLineupData("Lineup payload has no id"); + throw invalidLineupData("Lineup group has no id"); } if (lineupPublicId !== data.id) { - throw invalidLineupData("Lineup key does not match its payload"); - } - // The row must carry both ends hydration draws; a row without them would - // load as nothing and read as a deletion nobody made. - if (!hasEnd(data.origin, "agent") || !hasEnd(data.landing, "ability")) { - throw invalidLineupData("Lineup has no origin or landing"); + throw invalidLineupData("Lineup group key does not match its payload"); + } + const originIds = idsOfEntries(data.origins, "origins", "agent"); + const landingIds = idsOfEntries(data.landings, "landings", "ability"); + idsOfEntries(data.links, "links"); + const links = data.links as Array>; + // A group without a lineup would load as nothing and read as a deletion + // nobody made. + if (links.length === 0) { + throw invalidLineupData("Lineup group has no lineups"); + } + for (const link of links) { + if ( + typeof link.originId !== "string" || + !originIds.has(link.originId) || + typeof link.landingId !== "string" || + !landingIds.has(link.landingId) + ) { + throw invalidLineupData( + "A lineup names an origin or landing its group does not hold", + ); + } } return payload as LineupPayload; } -/// Whether [end] is an origin or landing: an id and the marker it places -/// under [markerKey]. -function hasEnd(end: unknown, markerKey: "agent" | "ability"): boolean { - return ( - isRecord(end) && - typeof end.id === "string" && - end.id.length > 0 && - isRecord(end[markerKey]) - ); +/// The ids of a lineup group's [field] (its origins, landings or links), +/// refusing the row unless [entries] is a list of objects each with an id +/// no other entry in it shares, and, given a [markerKey], the object it +/// places there. +function idsOfEntries( + entries: unknown, + field: string, + markerKey?: "agent" | "ability", +): Set { + if (!Array.isArray(entries)) { + throw invalidLineupData(`Lineup group ${field} is not a list`); + } + const ids = new Set(); + for (const entry of entries) { + if ( + !isRecord(entry) || + typeof entry.id !== "string" || + entry.id.length === 0 + ) { + throw invalidLineupData(`Lineup group ${field} has an entry with no id`); + } + if (ids.has(entry.id)) { + throw invalidLineupData(`Two of a lineup group's ${field} share an id`); + } + if (markerKey !== undefined && !isRecord(entry[markerKey])) { + throw invalidLineupData( + `Lineup group ${field} has an entry with no ${markerKey}`, + ); + } + ids.add(entry.id); + } + return ids; } /// A patch or reorder of a row a teammate deleted. Changing the tombstone @@ -1494,7 +1537,7 @@ async function reconcileExpectedAssets( await syncElementAssetReferences(ctx, row._id, row); } else { await syncLineupAssetReferences(ctx, row._id, row); - await syncLineupAgent(ctx, row._id, row); + await syncLineupAgents(ctx, row._id, row); } const now = Date.now(); const assetsBefore = referencedAssetIds(rowBefore); @@ -1582,7 +1625,9 @@ export const applyBatch = mutation({ const latest = await getTargetSnapshot(ctx, strategy, op); // An accepted op replays at the revision it landed at, not the // row's latest: a successor rebased onto a teammate's later edit - // would overwrite that edit unseen. + // would overwrite that edit unseen. An event that recorded no + // revision replays with none, for the same reason; the client then + // keeps its base revision and a later edit meets the usual check. const replayResult: OperationResult = existingEvent.status === "failed" ? { @@ -1598,7 +1643,7 @@ export const applyBatch = mutation({ latestRevision: latest?.revision, latestPayload: latest?.payload, } - : noop(existingEvent.appliedRevision ?? latest?.revision); + : noop(existingEvent.appliedRevision); results.push(toPublicResult(op, replayResult)); continue; } diff --git a/convex/pageTrash.test.ts b/convex/pageTrash.test.ts index 66a94b56..7b8012fd 100644 --- a/convex/pageTrash.test.ts +++ b/convex/pageTrash.test.ts @@ -11,7 +11,7 @@ import { CURRENT_CLOUD_PROTOCOL_VERSION } from "./lib/cloudProtocol"; import { PAGE_TRASH_RETENTION_MS } from "./lib/entities"; import { UNKNOWN_DISPLAY_NAME } from "./lib/profile"; import schema from "./schema"; -import { lineupPayload } from "./testContent.helpers"; +import { lineupsPayload, oneLineupPayload } from "./testContent.helpers"; import { modules } from "./test.setup"; const ensureCurrentUser = makeFunctionReference<"mutation">( @@ -108,22 +108,25 @@ function imagePayload(assetPublicId: string) { }; } -/// Two lineups thrown from one sova origin, the first showing an image. +/// Two lineup groups: one of two lineups thrown from one sova origin, the +/// first showing an image, and one of a single lineup from another. const lineupRows = [ { lineupPublicId: "k", - payload: lineupPayload("k", { - originId: "o", - landingId: "l", - agentType: "sova", - images: [{ id: "k-shot" }], + payload: lineupsPayload("k", { + origins: [{ id: "o", agentType: "sova" }], + landings: [{ id: "l" }, { id: "l2" }], + links: [ + { id: "k", originId: "o", landingId: "l", images: [{ id: "k-shot" }] }, + { id: "k-2", originId: "o", landingId: "l2" }, + ], }), }, { lineupPublicId: "k2", - payload: lineupPayload("k2", { - originId: "o", - landingId: "l2", + payload: oneLineupPayload("k2", { + originId: "o2", + landingId: "l3", agentType: "sova", }), }, @@ -548,7 +551,7 @@ describe("page trash", () => { type: "lineup.add", lineupPublicId: "k3", pagePublicId: pageB, - payload: lineupPayload("k3", { originId: "o", landingId: "l3" }), + payload: oneLineupPayload("k3", { originId: "o3", landingId: "l4" }), sortIndex: 3, }, { @@ -557,7 +560,13 @@ describe("page trash", () => { pagePublicId: pageB, payload: { ...lineupRows[0]!.payload, - data: { ...lineupRows[0]!.payload.data, name: "edited" }, + data: { + ...lineupRows[0]!.payload.data, + links: lineupRows[0]!.payload.data.links.map((link) => ({ + ...link, + name: "edited", + })), + }, }, expectedLineupRevision: 1, }, diff --git a/convex/referencedAssetIds.test.ts b/convex/referencedAssetIds.test.ts index f36049a4..1dd3719e 100644 --- a/convex/referencedAssetIds.test.ts +++ b/convex/referencedAssetIds.test.ts @@ -9,7 +9,7 @@ import type { DataModel } from "./_generated/dataModel"; import { markAssetReferencesReady } from "./lib/assetReferences"; import { CURRENT_CLOUD_PROTOCOL_VERSION } from "./lib/cloudProtocol"; import schema from "./schema"; -import { lineupPayload } from "./testContent.helpers"; +import { oneLineupPayload } from "./testContent.helpers"; import { modules } from "./test.setup"; const ensureCurrentUser = makeFunctionReference<"mutation">( @@ -109,7 +109,7 @@ async function seed(t: RootHarness): Promise { type: "lineup.add", lineupPublicId: "k", pagePublicId, - payload: lineupPayload("k", { images: [{ id: "shot" }] }), + payload: oneLineupPayload("k", { images: [{ id: "shot" }] }), sortIndex: 0, }, ]); diff --git a/convex/schema.ts b/convex/schema.ts index 3e593f5c..4904e2ad 100644 --- a/convex/schema.ts +++ b/convex/schema.ts @@ -105,11 +105,13 @@ export default defineSchema({ publicId: v.string(), strategyId: v.id("strategies"), pageId: v.id("pages"), - // One row per lineup, keyed by the lineup's id (payload.data.id). A key - // is unique within its strategy, not across strategies: a strategy - // copied on a device keeps its original's lineup ids. + // One row per lineup group: the lineups on one page joined through + // shared spots, holding its origins, landings and links. Keyed by the + // group's id (payload.data.id). A key is unique within its strategy, not + // across strategies: a strategy copied on a device keeps its original's + // lineup ids. // This shape replaced the origin/landing/link rows with no migration: - // production and dev held no lineup rows when it shipped (2026-10-01). + // production and dev held no lineup rows when it shipped (2026-10-02). payloadKind: lineupPayloadKindValidator, payloadVersion: v.number(), payload: lineupPayloadValidator, @@ -122,10 +124,11 @@ export default defineSchema({ .index("by_strategyId_and_publicId", ["strategyId", "publicId"]) .index("by_pageId", ["pageId"]) .index("by_deleted_and_updatedAt", ["deleted", "updatedAt"]), - // The agent each live lineup starts from, one small row per lineup, kept - // in step with every lineup write (see lib/strategyAgentSummary.ts). The - // strategy's agent summary reads these instead of the lineup rows, whose - // image lists can make reading them all exceed a transaction's limits. + // The agents a live lineup group starts from: one small row per origin in + // the group, kept in step with every lineup write (see + // lib/strategyAgentSummary.ts). The strategy's agent summary reads these + // instead of the lineup rows, whose image lists can make reading them all + // exceed a transaction's limits. lineupAgents: defineTable({ strategyId: v.id("strategies"), pageId: v.id("pages"), diff --git a/convex/strategies.ts b/convex/strategies.ts index 58215401..49208e8a 100644 --- a/convex/strategies.ts +++ b/convex/strategies.ts @@ -3,7 +3,7 @@ import { getConvexSize, v, type Value } from "convex/values"; import { agentTypesOf, deleteStrategyAgentSummary, - lineupAgentOf, + lineupAgentsOf, refreshStrategyAgentSummary, storeStrategyAgentSummary, } from "./lib/strategyAgentSummary"; @@ -537,10 +537,9 @@ function isJsonObject(value: unknown): value is Record { type LineupPayload = Doc<"lineups">["payload"]; /// New ids for a copied strategy's lineups: one map shared by every row and -/// every id in it. Lineups that share an origin or landing in the source -/// carry the same id for it, so their copies do too, and the copy draws -/// them as one spot again. Ids the source repeats across a lineup and its -/// ends (a landing may take its lineup's id) repeat in the copy the same way. +/// every id in it, so one source id always becomes one copy id. Ids the +/// source repeats within a group (a landing may take its lineup's id, a +/// link names its origin and landing) repeat in the copy the same way. function lineupIdMap() { const ids = new Map(); return (id: string): string => { @@ -550,26 +549,39 @@ function lineupIdMap() { }; } -/// A lineup row as the copy stores it: the lineup, its origin and its -/// landing under new ids, each marker's `lineUpID` following its end. +/// A lineup group row as the copy stores it: the group, its origins, +/// landings and links under new ids, each link naming its ends' new ids and +/// each marker's `lineUpID` following its spot. Everything else is kept as +/// it is. function copiedLineupRow( payload: LineupPayload, newId: (id: string) => string, ): { publicId: string; payload: LineupPayload } { const data = payload.data; const idOf = (value: unknown) => (typeof value === "string" ? value : ""); - const copiedEnd = (end: unknown, markerKey: "agent" | "ability") => { - if (!isJsonObject(end)) return end; - const id = newId(idOf(end.id)); - const marker = end[markerKey]; + const entries = (value: unknown): unknown[] => + Array.isArray(value) ? value : []; + const copiedSpot = (spot: unknown, markerKey: "agent" | "ability") => { + if (!isJsonObject(spot)) return spot; + const id = newId(idOf(spot.id)); + const marker = spot[markerKey]; return { - ...end, + ...spot, id, ...(isJsonObject(marker) ? { [markerKey]: { ...marker, lineUpID: id } } : {}), }; }; + const copiedLink = (link: unknown) => + isJsonObject(link) + ? { + ...link, + id: newId(idOf(link.id)), + originId: newId(idOf(link.originId)), + landingId: newId(idOf(link.landingId)), + } + : link; const id = newId(idOf(data.id)); return { publicId: id, @@ -578,8 +590,13 @@ function copiedLineupRow( data: { ...data, id, - origin: copiedEnd(data.origin, "agent"), - landing: copiedEnd(data.landing, "ability"), + origins: entries(data.origins).map((origin) => + copiedSpot(origin, "agent"), + ), + landings: entries(data.landings).map((landing) => + copiedSpot(landing, "ability"), + ), + links: entries(data.links).map(copiedLink), } as LineupData, }, }; @@ -828,8 +845,7 @@ export const duplicate = mutation({ budget.spend({ documents: assetIdsOfRow(copiedLineup).size }); const lineupId = await ctx.db.insert("lineups", copiedLineup); await insertAssetReferences(ctx, { lineupId }, copiedLineup); - const agent = lineupAgentOf(copiedLineup.payload); - if (agent !== null) { + for (const agent of lineupAgentsOf(copiedLineup.payload)) { budget.spend({ documents: 1 }); await ctx.db.insert("lineupAgents", { strategyId, diff --git a/convex/strategyDuplicate.test.ts b/convex/strategyDuplicate.test.ts index 7d2067f6..7f868d18 100644 --- a/convex/strategyDuplicate.test.ts +++ b/convex/strategyDuplicate.test.ts @@ -9,7 +9,7 @@ import type { DataModel } from "./_generated/dataModel"; import { markAssetReferencesReady } from "./lib/assetReferences"; import { CURRENT_CLOUD_PROTOCOL_VERSION } from "./lib/cloudProtocol"; import schema from "./schema"; -import { lineupPayload } from "./testContent.helpers"; +import { lineupsPayload, oneLineupPayload } from "./testContent.helpers"; import { modules } from "./test.setup"; const ensureCurrentUser = makeFunctionReference<"mutation">( @@ -72,19 +72,26 @@ async function createHarness(): Promise<{ return { t, owner, other }; } +// One lineup group holding one lineup. The group, its lineup and its +// landing share one id, as a landing made with its lineup does. const seedLineupData = { id: "item-1", - name: "Shock dart", - notes: "Two bounces", - images: [{ id: "lineup-image" }], - origin: { - id: "origin-1", - agent: { type: "sova", lineUpID: "origin-1" }, - }, - landing: { - id: "item-1", - ability: { type: "shock_dart", lineUpID: "item-1" }, - }, + origins: [ + { id: "origin-1", agent: { type: "sova", lineUpID: "origin-1" } }, + ], + landings: [ + { id: "item-1", ability: { type: "shock_dart", lineUpID: "item-1" } }, + ], + links: [ + { + id: "item-1", + originId: "origin-1", + landingId: "item-1", + name: "Shock dart", + notes: "Two bounces", + images: [{ id: "lineup-image" }], + }, + ], }; /// A two-page strategy with a placed image, a lineup with an image, an agent, @@ -186,15 +193,14 @@ async function seedSource(t: RootHarness, owner: Harness): Promise { pagePublicId: firstPage, expectedElementRevision: 1, }, - // One lineup showing an image. Its landing shares the lineup's id, as - // landings made with their lineup do. + // One lineup group showing an image. { opId: "add-lineup", type: "lineup.add", lineupPublicId: "item-1", pagePublicId: secondPage, payload: { - kind: "lineup", + kind: "lineups", payloadVersion: 1, data: seedLineupData, }, @@ -328,27 +334,30 @@ describe("strategies:duplicate", () => { expect(copy.lineups).toHaveLength(1); const lineup = copy.lineups[0]!; expect(lineup.pagePublicId).toBe(copySecondPage); - expect(lineup.payload.kind).toBe("lineup"); - // Fresh ids, still shared where the source shared them (the landing - // keeps the lineup's id), with each marker following its end. - const { id, origin, landing } = lineup.payload.data; + expect(lineup.payload.kind).toBe("lineups"); + // Fresh ids, still shared where the source shared them (the group, its + // lineup and its landing keep one id), with each link naming its ends' + // new ids and each marker following its spot. + const { id, origins } = lineup.payload.data; + const originId = origins[0].id; expect(lineup.publicId).toBe(id); expect(id).not.toBe("item-1"); - expect(origin.id).not.toBe("origin-1"); - expect(landing.id).toBe(id); + expect(originId).not.toBe("origin-1"); + expect(originId).not.toBe(id); expect(lineup.payload.data).toEqual({ id, - name: "Shock dart", - notes: "Two bounces", - images: [{ id: "lineup-image" }], - origin: { - id: origin.id, - agent: { type: "sova", lineUpID: origin.id }, - }, - landing: { - id, - ability: { type: "shock_dart", lineUpID: id }, - }, + origins: [{ id: originId, agent: { type: "sova", lineUpID: originId } }], + landings: [{ id, ability: { type: "shock_dart", lineUpID: id } }], + links: [ + { + id, + originId, + landingId: id, + name: "Shock dart", + notes: "Two bounces", + images: [{ id: "lineup-image" }], + }, + ], }); expect(await imageUrls(owner, "duplicate-copy")).toEqual({ @@ -414,7 +423,7 @@ describe("strategies:duplicate", () => { ).toEqual([]); }); - test("lineups that share spots copy with their sharing, details and image, and outlive the original", async () => { + test("a lineup group copies whole under new ids, with its shared spots, details and images, and outlives the original", async () => { vi.useFakeTimers(); const fetchMock = mockR2Deletes(); const { t, owner } = await createHarness(); @@ -425,51 +434,70 @@ describe("strategies:duplicate", () => { .withIndex("by_publicId", (q) => q.eq("publicId", source)) .unique(); const now = Date.now(); - await ctx.db.insert("imageAssets", { - publicId: "link-image", - provider: "r2", - strategyId: strategy!._id, - uploadAttemptPublicId: "link-image-attempt", - objectKey: `strategies/${source}/link-image.png`, - uploadStatus: "active", - fileExtension: ".png", - mimeType: "image/png", - width: 64, - height: 32, - byteSize: 100, - uploadedAt: now, - createdAt: now, - updatedAt: now, - }); + for (const publicId of ["link-image", "fan-out-image"]) { + await ctx.db.insert("imageAssets", { + publicId, + provider: "r2", + strategyId: strategy!._id, + uploadAttemptPublicId: `${publicId}-attempt`, + objectKey: `strategies/${source}/${publicId}.png`, + uploadStatus: "active", + fileExtension: ".png", + mimeType: "image/png", + width: 64, + height: 32, + byteSize: 100, + uploadedAt: now, + createdAt: now, + updatedAt: now, + }); + } }); // Two lineups from different origins into one landing (fan-in), and a - // third from the first one's origin to another landing (fan-out). The - // shared landing takes the first lineup's id, as a landing made with its - // lineup does. - const rows = [ - lineupPayload("link-a", { - originId: "origin-a", - landingId: "link-a", - name: "From heaven", - notes: "Run and throw", - images: [{ id: "link-image", fileExtension: ".png" }], - }), - lineupPayload("link-b", { - originId: "origin-b", - landingId: "link-a", - name: "From mid", - }), - lineupPayload("link-c", { - originId: "origin-a", - landingId: "landing-c", - name: "Fan out", - }), - ]; + // third from the first one's origin to another landing (fan-out): one + // group. The shared landing takes the first lineup's id, as a landing + // made with its lineup does, and the group takes it too. A second group + // on the page holds one lineup. + const group = lineupsPayload("link-a", { + origins: [ + { id: "origin-a", position: { dx: 10, dy: 10 } }, + { id: "origin-b", agentType: "brimstone", position: { dx: 20, dy: 20 } }, + ], + landings: [ + { id: "link-a", position: { dx: 300, dy: 300 } }, + { id: "landing-c", position: { dx: 500, dy: 500 } }, + ], + links: [ + { + id: "link-a", + originId: "origin-a", + landingId: "link-a", + name: "From heaven", + notes: "Run and throw", + images: [{ id: "link-image", fileExtension: ".png" }], + }, + { + id: "link-b", + originId: "origin-b", + landingId: "link-a", + name: "From mid", + youtubeLink: "https://youtu.be/b", + }, + { + id: "link-c", + originId: "origin-a", + landingId: "landing-c", + name: "Fan out", + images: [{ id: "fan-out-image", fileExtension: ".png" }], + }, + ], + }); + const lone = oneLineupPayload("lone", { name: "Lone" }); const added = (await owner.mutation(applyBatch, { ...protocol, strategyPublicId: source, clientId: "shared-spots", - ops: rows.map((payload, index) => ({ + ops: [group, lone].map((payload, index) => ({ opId: `add-${payload.data.id}`, type: "lineup.add", lineupPublicId: payload.data.id, @@ -478,9 +506,10 @@ describe("strategies:duplicate", () => { sortIndex: 10 + index, })), })) as { results: Array<{ status: string }> }; - expect(added.results.map((result) => result.status)).toEqual( - rows.map(() => "applied"), - ); + expect(added.results.map((result) => result.status)).toEqual([ + "applied", + "applied", + ]); await duplicate(owner); await deleteAndSweep(t, owner, source); @@ -489,65 +518,114 @@ describe("strategies:duplicate", () => { publicId: string; pagePublicId: string; sortIndex: number; - payload: { kind: string; data: Record }; + payload: { + kind: string; + payloadVersion: number; + data: Record; + }; }; const copy = (await owner.query(getFullSnapshot, { ...protocol, strategyPublicId: "duplicate-copy", - })) as { pages: Array<{ publicId: string; sortIndex: number }>; lineups: Row[] }; - // These sit on the first page (the seed's lineup is on the second). - const copyFirstPage = [...copy.pages].sort((a, b) => a.sortIndex - b.sortIndex)[0]! - .publicId; + })) as { + pages: Array<{ publicId: string; sortIndex: number }>; + lineups: Row[]; + }; + // These sit on the first page (the seed's group is on the second). + const copyFirstPage = [...copy.pages].sort( + (a, b) => a.sortIndex - b.sortIndex, + )[0]!.publicId; const copied = copy.lineups .filter((row) => row.pagePublicId === copyFirstPage) .sort((a, b) => a.sortIndex - b.sortIndex); - expect(copied.map((row) => row.payload.data.name)).toEqual([ - "From heaven", - "From mid", - "Fan out", - ]); - const oldIds = [ + expect(copied).toHaveLength(2); + const [copiedGroup, copiedLone] = copied as [Row, Row]; + + // Read the new ids off the copy, in the source's order. + const newIdOf = new Map(); + const remember = (sourceId: string, copyId: string) => { + // One source id always becomes one copy id. + expect(newIdOf.get(sourceId) ?? copyId).toBe(copyId); + newIdOf.set(sourceId, copyId); + }; + for (const [sourcePayload, row] of [ + [group, copiedGroup], + [lone, copiedLone], + ] as const) { + const data = row.payload.data; + expect(row.publicId).toBe(data.id); + remember(sourcePayload.data.id, data.id); + for (const field of ["origins", "landings", "links"] as const) { + expect(data[field]).toHaveLength(sourcePayload.data[field].length); + sourcePayload.data[field].forEach((entry, index) => + remember(entry.id, data[field][index].id), + ); + } + } + // Ids that were apart stay apart, and none is reused from the source. + const sourceIds = [...newIdOf.keys()].sort(); + const copyIds = [...newIdOf.values()]; + expect(sourceIds).toEqual([ + "landing-c", "link-a", "link-b", "link-c", + "lone", + "lone-landing", + "lone-origin", "origin-a", "origin-b", - "landing-c", - ]; - for (const row of copied) { - const { id, origin, landing } = row.payload.data; - // Each row is keyed by its new lineup id. - expect(row.publicId).toBe(id); - for (const newId of [id, origin.id, landing.id]) { - expect(oldIds).not.toContain(newId); - } - // Each marker follows its end's new id. - expect(origin.agent.lineUpID).toBe(origin.id); - expect(landing.ability.lineUpID).toBe(landing.id); - } - const [heaven, mid, fanOut] = copied.map((row) => row.payload.data); - // Fan-in survives: one new landing id, still the first lineup's id. - expect(mid!.landing.id).toBe(heaven!.landing.id); - expect(heaven!.landing.id).toBe(heaven!.id); - // Fan-out survives: one new origin id for both lineups from it. - expect(fanOut!.origin.id).toBe(heaven!.origin.id); - // And spots that were apart stay apart. - expect(mid!.origin.id).not.toBe(heaven!.origin.id); - expect(fanOut!.landing.id).not.toBe(heaven!.landing.id); - expect(new Set(copied.map((row) => row.publicId)).size).toBe(3); - // Details and the image come along. - expect(heaven).toMatchObject({ - notes: "Run and throw", - youtubeLink: "", - images: [{ id: "link-image", fileExtension: ".png" }], - }); - expect(mid!.images).toEqual([]); + ]); + expect(new Set(copyIds).size).toBe(copyIds.length); + for (const copyId of copyIds) expect(sourceIds).not.toContain(copyId); - // The copy's lineup image is its own reference and survived the - // original. + // Each copy is its source row under the new ids: each link naming its + // ends' new ids, each marker following its spot, every place, detail + // and image as it was. + const renamed = (payload: typeof group) => { + const id = (old: string) => newIdOf.get(old)!; + return { + ...payload, + data: { + id: id(payload.data.id), + origins: payload.data.origins.map((origin) => ({ + ...origin, + id: id(origin.id), + agent: { ...origin.agent, lineUpID: id(origin.id) }, + })), + landings: payload.data.landings.map((landing) => ({ + ...landing, + id: id(landing.id), + ability: { ...landing.ability, lineUpID: id(landing.id) }, + })), + links: payload.data.links.map((link) => ({ + ...link, + id: id(link.id), + originId: id(link.originId), + landingId: id(link.landingId), + })), + }, + }; + }; + expect(copiedGroup.payload).toEqual(renamed(group)); + expect(copiedLone.payload).toEqual(renamed(lone)); + // Fan-in and fan-out survive: both lineups into the shared landing name + // one new id, still the group's and the first lineup's, and both + // lineups from the shared origin name one new id. + const links = copiedGroup.payload.data.links; + expect(links[1].landingId).toBe(links[0].landingId); + expect(links[0].landingId).toBe(copiedGroup.publicId); + expect(links[0].id).toBe(copiedGroup.publicId); + expect(links[2].originId).toBe(links[0].originId); + + // The copy's lineup images are its own references, one per image across + // every link, and survived the original. expect(fetchMock).not.toHaveBeenCalled(); const urls = await imageUrls(owner, "duplicate-copy"); expect(urls["link-image"]).toEqual(expect.stringContaining("link-image.png")); + expect(urls["fan-out-image"]).toEqual( + expect.stringContaining("fan-out-image.png"), + ); const lineupReferences = await t.run(async (ctx) => { const copyRow = await ctx.db .query("strategies") @@ -562,149 +640,19 @@ describe("strategies:duplicate", () => { .map((reference) => reference.assetPublicId) .sort(); }); - expect(lineupReferences).toEqual(["lineup-image", "link-image"]); - - await deleteAndSweep(t, owner, "duplicate-copy"); - expect(deletedKeys(fetchMock)).toContain( - `/duplicate-bucket/strategies/${source}/link-image.png`, - ); - }); - - test("a duplicate copies each lineup row exactly, even where rows sharing a spot disagree on it", async () => { - const { owner } = await createHarness(); - const exact = "exact-source"; - const exactPage = "exact-source-page"; - await owner.mutation(createStrategy, { - ...protocol, - publicId: exact, - name: "Exact", - mapData: "split", - initialPagePublicId: exactPage, - initialPageName: "Setup", - initialPageIsAttack: true, - }); - // A and B land in one spot but their rows hold it in different places; - // A and C start from one origin, also held in different places. C's - // landing shares C's id. Each row is drawn from its own copy, so the - // duplicate must carry every row over as it is, whatever the new ids. - const rows = [ - lineupPayload("lineup-a", { - originId: "origin-a", - landingId: "spot", - originPosition: { dx: 10, dy: 10 }, - landingPosition: { dx: 300, dy: 300 }, - name: "A", - notes: "Jump throw", - }), - lineupPayload("lineup-b", { - originId: "origin-b", - landingId: "spot", - agentType: "brimstone", - originPosition: { dx: 20, dy: 20 }, - landingPosition: { dx: 100, dy: 100 }, - name: "B", - youtubeLink: "https://youtu.be/b", - }), - lineupPayload("lineup-c", { - originId: "origin-a", - landingId: "lineup-c", - originPosition: { dx: 40, dy: 40 }, - landingPosition: { dx: 500, dy: 500 }, - name: "C", - }), - ]; - const added = (await owner.mutation(applyBatch, { - ...protocol, - strategyPublicId: exact, - clientId: "exact", - ops: rows.map((payload, index) => ({ - opId: `add-${payload.data.id}`, - type: "lineup.add", - lineupPublicId: payload.data.id, - pagePublicId: exactPage, - payload, - sortIndex: index, - })), - })) as { results: Array<{ status: string }> }; - expect(added.results.map((result) => result.status)).toEqual( - rows.map(() => "applied"), - ); - - await owner.mutation(duplicateStrategy, { - ...protocol, - sourceStrategyPublicId: exact, - publicId: "exact-copy", - name: "Exact (Copy)", - }); - type Row = { publicId: string; payload: { data: Record } }; - const copied = ( - (await owner.query(getFullSnapshot, { - ...protocol, - strategyPublicId: "exact-copy", - })) as { lineups: Row[] } - ).lineups; - expect(copied.map((row) => row.payload.data.name).sort()).toEqual([ - "A", - "B", - "C", + expect(lineupReferences).toEqual([ + "fan-out-image", + "lineup-image", + "link-image", ]); - // Pair each source row with its copy and read off the new ids. - const newIdOf = new Map(); - const remember = (sourceId: string, copyId: string) => { - // One source id always becomes one copy id. - expect(newIdOf.get(sourceId) ?? copyId).toBe(copyId); - newIdOf.set(sourceId, copyId); - }; - const pairs = rows.map((sourcePayload) => { - const copy = copied.find( - (row) => row.payload.data.name === sourcePayload.data.name, - )!; - const data = copy.payload.data; - expect(copy.publicId).toBe(data.id); - remember(sourcePayload.data.id, data.id); - remember(sourcePayload.data.origin.id, data.origin.id); - remember(sourcePayload.data.landing.id, data.landing.id); - return { sourcePayload, copy }; - }); - // Ids that were apart stay apart, and none is reused from the source. - const sourceIds = [...newIdOf.keys()].sort(); - const copyIds = [...newIdOf.values()]; - expect(sourceIds).toEqual( - ["lineup-a", "lineup-b", "lineup-c", "origin-a", "origin-b", "spot"], + await deleteAndSweep(t, owner, "duplicate-copy"); + expect(deletedKeys(fetchMock)).toEqual( + expect.arrayContaining([ + `/duplicate-bucket/strategies/${source}/link-image.png`, + `/duplicate-bucket/strategies/${source}/fan-out-image.png`, + ]), ); - expect(new Set(copyIds).size).toBe(copyIds.length); - for (const copyId of copyIds) expect(sourceIds).not.toContain(copyId); - - // Each copy is its source row under the new ids, each marker following - // its end, every place and detail as it was. - for (const { sourcePayload, copy } of pairs) { - const data = sourcePayload.data; - const originId = newIdOf.get(data.origin.id)!; - const landingId = newIdOf.get(data.landing.id)!; - expect(copy.payload).toEqual({ - ...sourcePayload, - data: { - ...data, - id: newIdOf.get(data.id)!, - origin: { - ...data.origin, - id: originId, - agent: { ...data.origin.agent, lineUpID: originId }, - }, - landing: { - ...data.landing, - id: landingId, - ability: { ...data.landing.ability, lineUpID: landingId }, - }, - }, - }); - } - // So the copy's shared spot disagrees exactly as the original's did. - const [copyA, copyB] = pairs.map((pair) => pair.copy.payload.data); - expect(copyA!.landing.id).toBe(copyB!.landing.id); - expect(copyA!.landing.ability.position).toEqual({ dx: 300, dy: 300 }); - expect(copyB!.landing.ability.position).toEqual({ dx: 100, dy: 100 }); }); test("an image whose tombstone is purged is reclaimed once nothing shows it", async () => { @@ -802,7 +750,7 @@ describe("strategies:duplicate", () => { ], }); vi.setSystemTime(Date.now() + 31 * day); - const showing = lineupPayload("late-link", { + const showing = oneLineupPayload("late-link", { images: [{ id: "placed-image" }], }); await owner.mutation(applyBatch, { @@ -1276,7 +1224,7 @@ describe("images placed before their upload", () => { type: "lineup.add", lineupPublicId: "link-shows-shared", pagePublicId: firstPage, - payload: lineupPayload("link-shows-shared", { + payload: oneLineupPayload("link-shows-shared", { images: [{ id: "link-late-image", fileExtension: ".png" }], }), sortIndex: 2, @@ -1317,7 +1265,7 @@ describe("images placed before their upload", () => { type: "lineup.add", lineupPublicId: "shows-b", pagePublicId: firstPage, - payload: lineupPayload("shows-b", { + payload: oneLineupPayload("shows-b", { images: [{ id: "pending-b", fileExtension: ".png" }], }), sortIndex: 5, @@ -1361,7 +1309,7 @@ describe("images placed before their upload", () => { type: "lineup.add", lineupPublicId: "lineup-shows-batch", pagePublicId: firstPage, - payload: lineupPayload("lineup-shows-batch", { + payload: oneLineupPayload("lineup-shows-batch", { images: [{ id: "batch-shown" }], }), sortIndex: 3, @@ -1399,7 +1347,7 @@ describe("images placed before their upload", () => { type: "lineup.add", lineupPublicId: "late-link", pagePublicId: firstPage, - payload: lineupPayload("late-link", { + payload: oneLineupPayload("late-link", { images: [{ id: "late-link-image", fileExtension: ".png" }], }), sortIndex: 1, diff --git a/convex/syncBoundaries.test.ts b/convex/syncBoundaries.test.ts index f5baa9c7..d5365f43 100644 --- a/convex/syncBoundaries.test.ts +++ b/convex/syncBoundaries.test.ts @@ -18,7 +18,7 @@ import schema from "./schema"; import { insertElement, insertLineup, - lineupPayload, + oneLineupPayload, } from "./testContent.helpers"; import { modules } from "./test.setup"; @@ -79,9 +79,9 @@ function imagePayload(assetPublicId: string) { }; } -/// A lineup showing one image; its row key is [lineupId]. +/// A group of one lineup showing one image; its row key is [lineupId]. function lineupShowing(assetPublicId: string, lineupId = "restorable") { - return lineupPayload(lineupId, { + return oneLineupPayload(lineupId, { originId: "origin", landingId: "landing", name: "B lineup", @@ -355,7 +355,7 @@ async function seedTwoPageContent(t: Harness, owner: Harness) { publicId: "lineup-b", strategyId: strategy._id, pageId: pageBId, - payloadKind: "lineup", + payloadKind: "lineups", payloadVersion: 1, payload: lineupShowing(assetB, "lineup-b"), sortIndex: 0, @@ -1258,7 +1258,7 @@ describe("record-scoped write contract", () => { publicId: string; revision: number; deleted: boolean; - payload: { data: { images: Array<{ id: string }> } }; + payload: { data: { links: Array<{ images: Array<{ id: string }> }> } }; }>; }; expect(snapshot.elements).toMatchObject([ @@ -1275,7 +1275,7 @@ describe("record-scoped write contract", () => { revision: 3, deleted: false, payload: { - data: { images: [{ id: restoredAsset }] }, + data: { links: [{ images: [{ id: restoredAsset }] }] }, }, }, ]); @@ -1398,10 +1398,10 @@ describe("record-scoped write contract", () => { expect(replayed).toMatchObject({ revision: 2, reused: true }); }); - test("a lineup without its landing is refused", async () => { + test("a lineup group whose lineup names a landing it does not hold is refused", async () => { const { owner } = await createHarness(); await createBaseStrategy(owner); - const { landing: _landing, ...withoutLanding } = lineupShowing( + const { landings: _landings, ...withoutLandings } = lineupShowing( "asset-empty", "lineup-empty", ).data; @@ -1413,7 +1413,11 @@ describe("record-scoped write contract", () => { entityType: "lineup", entityPublicId: "lineup-empty", pagePublicId: pageA, - payload: { kind: "lineup", payloadVersion: 1, data: withoutLanding }, + payload: { + kind: "lineups", + payloadVersion: 1, + data: { ...withoutLandings, landings: [] }, + }, }, { opId: "add-lineup", @@ -1464,23 +1468,37 @@ describe("record-scoped write contract", () => { rawCode: "INVALID_LINEUP_PAYLOAD_KIND", }, ]); - // The legacy group before it is refused whole by argument validation. - await expect( - applyOps(owner, "old-client", [ - { - opId: "add-legacy-group", - kind: "add", - entityType: "lineup", - entityPublicId: "legacy-group", - pagePublicId: pageA, - payload: { - kind: "lineupGroup", - payloadVersion: 1, - data: { id: "legacy-group", items: [{ id: "item" }] }, - }, + // The legacy group before it, and the one-row-per-lineup shape tried + // before groups, are refused whole by argument validation. + for (const payload of [ + { + kind: "lineupGroup", + payloadVersion: 1, + data: { id: "legacy-group", items: [{ id: "item" }] }, + }, + { + kind: "lineup", + payloadVersion: 1, + data: { + id: "legacy-group", + origin: { id: "o", agent: { type: "sova" } }, + landing: { id: "l", ability: {} }, }, - ]), - ).rejects.toThrow(/Validator error|ArgumentValidationError/); + }, + ]) { + await expect( + applyOps(owner, "old-client", [ + { + opId: `add-legacy-${payload.kind}`, + kind: "add", + entityType: "lineup", + entityPublicId: "legacy-group", + pagePublicId: pageA, + payload, + }, + ]), + ).rejects.toThrow(/Validator error|ArgumentValidationError/); + } const snapshot = (await owner.query(getPageSnapshot, { clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, strategyPublicId, @@ -1532,7 +1550,7 @@ describe("record-scoped write contract", () => { function contentPayload(entity: "element" | "lineup", id: string, label: string) { return entity === "element" ? textPayload(label) - : lineupPayload(id, { name: label }); + : oneLineupPayload(id, { name: label }); } type ContentRow = { diff --git a/convex/testContent.helpers.ts b/convex/testContent.helpers.ts index ffea8ff4..0d50d64a 100644 --- a/convex/testContent.helpers.ts +++ b/convex/testContent.helpers.ts @@ -9,7 +9,7 @@ import { syncElementAssetReferences, syncLineupAssetReferences, } from "./lib/assetReferences"; -import { syncLineupAgent } from "./lib/strategyAgentSummary"; +import { syncLineupAgents } from "./lib/strategyAgentSummary"; export async function insertElement( ctx: MutationCtx, @@ -26,55 +26,114 @@ export async function insertLineup( ): Promise> { const id = await ctx.db.insert("lineups", lineup); await syncLineupAssetReferences(ctx, id, lineup); - await syncLineupAgent(ctx, id, lineup); + await syncLineupAgents(ctx, id, lineup); return id; } -export type TestLineup = { - originId?: string; - landingId?: string; +type Position = { dx: number; dy: number }; + +export type TestOrigin = { + id: string; agentType?: string; - originPosition?: { dx: number; dy: number }; - landingPosition?: { dx: number; dy: number }; + position?: Position; +}; + +export type TestLanding = { id: string; position?: Position }; + +export type TestLink = { + id: string; + originId: string; + landingId: string; name?: string; youtubeLink?: string; notes?: string; images?: Array<{ id: string; fileExtension?: string }>; }; -/// A lineup row's payload as a client writes it: the lineup's details and -/// whole copies of its origin (the agent) and landing (the ability), each -/// marker's `lineUpID` naming its end. Lineups that share a spot pass the -/// same originId or landingId. The row's key is [id]. -export function lineupPayload(id: string, lineup: TestLineup = {}) { - const originId = lineup.originId ?? `${id}-origin`; - const landingId = lineup.landingId ?? `${id}-landing`; +export type TestLineupGroup = { + origins: TestOrigin[]; + landings: TestLanding[]; + links: TestLink[]; +}; + +/// A lineup group row's payload as a client writes it: the group's id (the +/// row's key), its origins (each placing an agent), landings (each placing +/// an ability) and links, each in the shape of the Dart model's toJson() +/// (LineUpOrigin, LineUpLanding, LineUpLink), each marker's `lineUpID` +/// naming its spot. +export function lineupsPayload(id: string, group: TestLineupGroup) { return { - kind: "lineup" as const, + kind: "lineups" as const, payloadVersion: 1, data: { id, - name: lineup.name ?? "", - youtubeLink: lineup.youtubeLink ?? "", - notes: lineup.notes ?? "", - images: lineup.images ?? [], - origin: { - id: originId, + origins: group.origins.map((origin) => ({ + id: origin.id, agent: { - id: `agent-${originId}`, - type: lineup.agentType ?? "sova", - position: lineup.originPosition ?? { dx: 0, dy: 0 }, - lineUpID: originId, + id: `agent-${origin.id}`, + type: origin.agentType ?? "sova", + position: origin.position ?? { dx: 0, dy: 0 }, + lineUpID: origin.id, }, - }, - landing: { - id: landingId, + })), + landings: group.landings.map((landing) => ({ + id: landing.id, ability: { - id: `ability-${landingId}`, - position: lineup.landingPosition ?? { dx: 0, dy: 0 }, - lineUpID: landingId, + id: `ability-${landing.id}`, + position: landing.position ?? { dx: 0, dy: 0 }, + lineUpID: landing.id, }, - }, + })), + links: group.links.map((link) => ({ + id: link.id, + originId: link.originId, + landingId: link.landingId, + name: link.name ?? "", + youtubeLink: link.youtubeLink ?? "", + notes: link.notes ?? "", + images: link.images ?? [], + })), }, }; } + +export type TestLineup = { + originId?: string; + landingId?: string; + agentType?: string; + originPosition?: Position; + landingPosition?: Position; + name?: string; + youtubeLink?: string; + notes?: string; + images?: Array<{ id: string; fileExtension?: string }>; +}; + +/// A group row holding one lineup: one origin, one landing and the link +/// between them. The group and its link take [id]; the spots default to +/// `-origin` and `-landing`. +export function oneLineupPayload(id: string, lineup: TestLineup = {}) { + const originId = lineup.originId ?? `${id}-origin`; + const landingId = lineup.landingId ?? `${id}-landing`; + return lineupsPayload(id, { + origins: [ + { + id: originId, + agentType: lineup.agentType, + position: lineup.originPosition, + }, + ], + landings: [{ id: landingId, position: lineup.landingPosition }], + links: [ + { + id, + originId, + landingId, + name: lineup.name, + youtubeLink: lineup.youtubeLink, + notes: lineup.notes, + images: lineup.images, + }, + ], + }); +} diff --git a/lib/collab/cloud_lineup_rows.dart b/lib/collab/cloud_lineup_rows.dart index cf568b0a..6c63fbb8 100644 --- a/lib/collab/cloud_lineup_rows.dart +++ b/lib/collab/cloud_lineup_rows.dart @@ -1,140 +1,218 @@ -import 'dart:convert'; - -import 'package:icarus/collab/canonical_json.dart'; import 'package:icarus/collab/collab_models.dart'; import 'package:icarus/const/line_provider.dart'; +import 'package:uuid/uuid.dart'; -/// One cloud row per lineup (a [LineUpLink]): its name, video, notes and -/// images, and whole copies of its origin and landing. +/// One cloud row per lineup group: lineups that share a standing spot or a +/// landing spot, stored together with those spots. +/// +/// A shared spot lives in exactly one row, so it can never disagree with +/// itself, and the row's one revision guards every lineup on it: two +/// clients changing the same group meet as an ordinary conflict. A row never +/// names anything outside itself. /// -/// Lineups that share a spot (several lineups from one origin, or into one -/// landing) each carry their own copy of it, under the spot's id. The server -/// never joins rows, so every row can be drawn on its own, and a lineup's -/// row only ever changes when the user changes that lineup or its spot. +/// A group only grows or merges, never splits: deleting the lineup that +/// joined two halves leaves both halves in the row. That keeps every edit a +/// write to exactly the row the user's lineups already live in. class CloudLineupRow { const CloudLineupRow({required this.publicId, required this.payload}); CloudLineupRow.remote(RemoteLineup lineup) : this(publicId: lineup.publicId, payload: lineup.payload); - /// The lineup's id, as on the canvas and in `payload.data.id`. + /// The group's id, as in `payload.data.id`. final String publicId; final CloudPayload payload; } -/// Local ids standing for real origin and landing ids, alias to real id. -/// They exist only on the canvas (see [lineUpGraphFromCloudRows]); rows are -/// always written under the real id. -class CloudLineupAliases { - const CloudLineupAliases({this.origins = const {}, this.landings = const {}}); - - static const none = CloudLineupAliases(); - - final Map origins; - final Map landings; - - bool get isEmpty => origins.isEmpty && landings.isEmpty; +/// A page's lineups as cloud rows describe them, and the group each lineup +/// and spot was drawn from (lineup, origin or landing id to group id). +class CloudLineups { + const CloudLineups({required this.graph, this.groupOf = const {}}); - CloudLineupAliases followedBy(CloudLineupAliases other) { - if (other.isEmpty) return this; - return CloudLineupAliases( - origins: {...origins, ...other.origins}, - landings: {...landings, ...other.landings}, - ); - } + final LineUpGraph graph; + final Map groupOf; } -/// A page's lineups as cloud rows describe them, and the aliases drawing -/// them took. -class CloudLineups { - const CloudLineups({ - required this.graph, - this.aliases = CloudLineupAliases.none, - }); +/// The rows that store a page's lineups, and the group each lineup and spot +/// went into. +class CloudLineupRows { + const CloudLineupRows({required this.rows, required this.groupOf}); - final LineUpGraph graph; - final CloudLineupAliases aliases; + final List rows; + final Map groupOf; } -/// A page's lineups as its live [rows] describe them. +/// A page's lineups as its live [rows] describe them, rows in order. /// -/// Links come from the rows, in their order. The copies the rows carry of -/// one origin (or landing) id are grouped by value. Copies that agree, as -/// they do unless a change reached only some of a spot's lineups, are drawn -/// as one spot under the real id. Copies that disagree are drawn apart, one -/// spot per value: the value the row with the smallest lineup id carries -/// keeps the real id, and every other value is drawn under the alias -/// `@`. Every client draws the same -/// spots from the same rows, and nothing is written for it: each lineup -/// keeps its own copy until the user changes it. Spots appear in the order -/// the rows first carry them. +/// Ids are unique within a row but nothing stops two rows naming the same +/// one (two clients merging groups at once, say). The first row to name a +/// spot or lineup draws it; a lineup in a later row aimed at that spot is +/// drawn to it too. /// /// Throws a [FormatException] naming the row when one cannot be read. CloudLineups lineUpGraphFromCloudRows(Iterable rows) { - final read = [for (final row in rows) _readLineupRow(row)]; - final originIds = _spotIds([ - for (final (link, origin, _) in read) (link.id, origin.id, origin.toJson()), - ]); - final landingIds = _spotIds([ - for (final (link, _, landing) in read) - (link.id, landing.id, landing.toJson()), - ]); - final origins = {}; final landings = {}; - final links = []; - for (final (link, origin, landing) in read) { - final originId = originIds.byLineup[link.id]!; - final landingId = landingIds.byLineup[link.id]!; - origins.putIfAbsent(originId, () => _originAs(origin, originId)); - landings.putIfAbsent(landingId, () => _landingAs(landing, landingId)); - links.add(link.copyWith(originId: originId, landingId: landingId)); + final links = {}; + final groupOf = {}; + for (final row in rows) { + final group = _readGroupRow(row); + for (final origin in group.origins) { + if (origins.containsKey(origin.id)) continue; + origins[origin.id] = origin; + groupOf[origin.id] = row.publicId; + } + for (final landing in group.landings) { + if (landings.containsKey(landing.id)) continue; + landings[landing.id] = landing; + groupOf[landing.id] = row.publicId; + } + for (final link in group.links) { + if (links.containsKey(link.id)) continue; + links[link.id] = link; + groupOf[link.id] = row.publicId; + } } return CloudLineups( graph: LineUpGraph( origins: origins.values.toList(), landings: landings.values.toList(), - links: links, - ), - aliases: CloudLineupAliases( - origins: originIds.aliases, - landings: landingIds.aliases, + links: links.values.toList(), ), + groupOf: groupOf, ); } -/// The rows that store [graph]: one per link, in link order, each carrying -/// its origin and landing as they are now, under their real ids ([aliases] -/// maps the canvas's aliases back). A link whose origin or landing is -/// missing draws nothing on the canvas, so it has no row either. -List cloudLineupRows( +/// The rows that store [graph], given the group each lineup and spot was in +/// before ([groupOf], from the rows the canvas was drawn from and earlier +/// calls). +/// +/// Lineups connected through a spot share a row, and so do lineups that +/// shared one before, so a group never splits. A group connected to two +/// groups (only a merge from two clients makes one) takes the smaller id +/// and the other row is no longer written. A group with no lineup that was +/// in one before is new: it takes its smallest lineup id that is not +/// already a group's id, here, in [groupOf] or in [takenGroupIds] (or a +/// fresh id, when every one of them is). +/// +/// A lineup whose origin or landing is missing draws nothing on the canvas, +/// so it is not written; neither is a spot no lineup is aimed at. Rows come +/// in the order of their first lineup, their contents in [graph]'s order. +CloudLineupRows cloudLineupRows( LineUpGraph graph, { - CloudLineupAliases aliases = CloudLineupAliases.none, + Map groupOf = const {}, + Set takenGroupIds = const {}, }) { - final origins = { - for (final origin in graph.origins) - origin.id: _originAs(origin, aliases.origins[origin.id] ?? origin.id), - }; - final landings = { - for (final landing in graph.landings) - landing.id: - _landingAs(landing, aliases.landings[landing.id] ?? landing.id), - }; - return [ + final originIds = {for (final origin in graph.origins) origin.id}; + final landingIds = {for (final landing in graph.landings) landing.id}; + final links = [ for (final link in graph.links) - if ((origins[link.originId], landings[link.landingId]) - case (final origin?, final landing?)) - CloudLineupRow( - publicId: link.id, - payload: cloudLineupPayload({ - ...link.toJson() - ..remove('originId') - ..remove('landingId'), - 'origin': origin.toJson(), - 'landing': landing.toJson(), - }), - ), + if (originIds.contains(link.originId) && + landingIds.contains(link.landingId)) + link, ]; + + // Union-find over lineups, spots and earlier groups, each kind in its own + // namespace so equal ids of different kinds stay apart. + final parent = {}; + String find(String node) { + final up = parent[node] ?? node; + if (up == node) return node; + return parent[node] = find(up); + } + + void union(String a, String b) { + parent.putIfAbsent(a, () => a); + parent.putIfAbsent(b, () => b); + final rootA = find(a); + final rootB = find(b); + if (rootA != rootB) parent[rootA] = rootB; + } + + void joinEarlierGroup(String node, String id) { + if (groupOf[id] case final group?) union(node, 'g:$group'); + } + + for (final link in links) { + final node = 'k:${link.id}'; + union(node, 'o:${link.originId}'); + union(node, 'l:${link.landingId}'); + joinEarlierGroup(node, link.id); + joinEarlierGroup('o:${link.originId}', link.originId); + joinEarlierGroup('l:${link.landingId}', link.landingId); + } + + // Each component's links in graph order, and the earlier groups in it. + final linksByRoot = >{}; + for (final link in links) { + (linksByRoot[find('k:${link.id}')] ??= []).add(link); + } + final earlierByRoot = >{}; + for (final node in parent.keys) { + if (node.startsWith('g:')) { + (earlierByRoot[find(node)] ??= []).add(node.substring(2)); + } + } + + String smallest(Iterable ids) => + ids.reduce((a, b) => a.compareTo(b) <= 0 ? a : b); + + final idByRoot = {}; + final used = {...takenGroupIds, ...groupOf.values}; + for (final MapEntry(key: root, value: earlier) in earlierByRoot.entries) { + if (!linksByRoot.containsKey(root)) continue; + final id = smallest(earlier); + idByRoot[root] = id; + used.addAll(earlier); + } + for (final MapEntry(key: root, value: groupLinks) in linksByRoot.entries) { + if (idByRoot.containsKey(root)) continue; + final candidates = [for (final link in groupLinks) link.id]..sort(); + final id = candidates.firstWhere( + (candidate) => !used.contains(candidate), + orElse: () => const Uuid().v4(), + ); + idByRoot[root] = id; + used.add(id); + } + + final originsById = {for (final origin in graph.origins) origin.id: origin}; + final landingsById = { + for (final landing in graph.landings) landing.id: landing, + }; + final assigned = {}; + final rows = []; + for (final MapEntry(key: root, value: groupLinks) in linksByRoot.entries) { + final id = idByRoot[root]!; + final usedOrigins = {for (final link in groupLinks) link.originId}; + final usedLandings = {for (final link in groupLinks) link.landingId}; + final origins = [ + for (final origin in graph.origins) + if (usedOrigins.contains(origin.id)) originsById[origin.id]!, + ]; + final landings = [ + for (final landing in graph.landings) + if (usedLandings.contains(landing.id)) landingsById[landing.id]!, + ]; + for (final itemId in [...usedOrigins, ...usedLandings]) { + assigned[itemId] = id; + } + for (final link in groupLinks) { + assigned[link.id] = id; + } + rows.add( + CloudLineupRow( + publicId: id, + payload: cloudLineupsPayload({ + 'id': id, + 'origins': [for (final origin in origins) origin.toJson()], + 'landings': [for (final landing in landings) landing.toJson()], + 'links': [for (final link in groupLinks) link.toJson()], + }), + ), + ); + } + return CloudLineupRows(rows: rows, groupOf: assigned); } /// A page's lineups as its [lineups] on the server describe them. Deleted @@ -146,109 +224,71 @@ LineUpGraph lineUpGraphFromRemoteLineups(Iterable lineups) { ]).graph; } -/// Whether [op] is a lineup change in the cloud format before one row per -/// lineup: an origin, landing or link row keyed `:`. An outbox -/// written by such a build can still hold one; it is never sent. +/// Whether [op] is a lineup change in a cloud format before one row per +/// lineup group: an origin, landing or link row keyed `:`. An +/// outbox written by such a build can still hold one; it is never sent. bool isRetiredCloudLineupOp(StrategyOp op) { if (op.entityType != StrategyOpEntityType.lineup) return false; final payload = op.payload; - return (payload is Map && payload['kind'] != cloudLineupPayloadKind) || + return (payload is Map && payload['kind'] != cloudLineupsPayloadKind) || _retiredLineupKey.hasMatch(op.entityPublicId ?? ''); } final _retiredLineupKey = RegExp(r'^lineup(Origin|Landing|Link):'); -/// The spot id each lineup's copy of one kind of end is drawn under, and the -/// aliases among them; see [lineUpGraphFromCloudRows]. -({Map byLineup, Map aliases}) _spotIds( - List<(String lineupId, String endId, Map value)> copies, -) { - // End id -> value -> the lineups carrying that value. - final groups = >>{}; - for (final (lineupId, endId, value) in copies) { - ((groups[endId] ??= {})[_valueKey(value)] ??= []).add(lineupId); - } - final byLineup = {}; - final aliases = {}; - String smallest(List ids) => ids.reduce( - (a, b) => a.compareTo(b) <= 0 ? a : b, - ); - for (final MapEntry(key: endId, value: byValue) in groups.entries) { - final firsts = { - for (final MapEntry(:key, value: lineups) in byValue.entries) - key: smallest(lineups), - }; - final realValue = firsts.entries - .reduce((a, b) => a.value.compareTo(b.value) <= 0 ? a : b) - .key; - for (final MapEntry(key: value, value: lineups) in byValue.entries) { - final id = value == realValue ? endId : '$endId@${firsts[value]}'; - if (id != endId) aliases[id] = endId; - for (final lineupId in lineups) { - byLineup[lineupId] = id; - } - } - } - return (byLineup: byLineup, aliases: aliases); -} - -String _valueKey(Map value) => - canonicalCloudJsonEncode(jsonDecode(jsonEncode(value))); - -/// [origin] under [id], its agent's `lineUpID` following when it named the -/// origin's old id. -LineUpOrigin _originAs(LineUpOrigin origin, String id) { - if (id == origin.id) return origin; - final agent = origin.agent; - return LineUpOrigin( - id: id, - agent: agent.lineUpID == origin.id - ? (agent.copyWith(lineUpID: id)..isDeleted = agent.isDeleted) - : agent, - ); -} - -/// [landing] under [id], its ability's `lineUpID` following when it named -/// the landing's old id. -LineUpLanding _landingAs(LineUpLanding landing, String id) { - if (id == landing.id) return landing; - final ability = landing.ability; - return LineUpLanding( - id: id, - ability: ability.lineUpID == landing.id - ? (ability.copyWith(lineUpID: id)..isDeleted = ability.isDeleted) - : ability, - ); -} - -(LineUpLink, LineUpOrigin, LineUpLanding) _readLineupRow(CloudLineupRow row) { +({ + List origins, + List landings, + List links, +}) _readGroupRow(CloudLineupRow row) { try { final kind = row.payload['kind']; - if (kind != cloudLineupPayloadKind) { + if (kind != cloudLineupsPayloadKind) { throw FormatException('unknown lineup kind $kind'); } final data = cloudPayloadData(row.payload); - final origin = LineUpOrigin.fromJson(_object(data['origin'], 'origin')); - final landing = LineUpLanding.fromJson(_object(data['landing'], 'landing')); - final link = LineUpLink.fromJson({ - ...data, - 'originId': origin.id, - 'landingId': landing.id, - }); - if (link.id != row.publicId) { - throw FormatException('it holds lineup ${link.id}'); + if (data['id'] != row.publicId) { + throw FormatException('it holds group ${data['id']}'); } - return (link, origin, landing); + final group = ( + origins: [ + for (final json in _objects(data['origins'], 'origins')) + LineUpOrigin.fromJson(json), + ], + landings: [ + for (final json in _objects(data['landings'], 'landings')) + LineUpLanding.fromJson(json), + ], + links: [ + for (final json in _objects(data['links'], 'links')) + LineUpLink.fromJson(json), + ], + ); + final originIds = {for (final origin in group.origins) origin.id}; + final landingIds = {for (final landing in group.landings) landing.id}; + for (final link in group.links) { + if (!originIds.contains(link.originId) || + !landingIds.contains(link.landingId)) { + throw FormatException('lineup ${link.id} is aimed outside the row'); + } + } + return group; } catch (error, stackTrace) { Error.throwWithStackTrace( - FormatException('Cloud lineup ${row.publicId} could not be read: ' + FormatException('Cloud lineup group ${row.publicId} could not be read: ' '$error'), stackTrace, ); } } -Map _object(Object? value, String name) { - if (value is! Map) throw FormatException('it has no $name'); - return Map.from(value); +List> _objects(Object? value, String name) { + if (value is! List) throw FormatException('it has no $name'); + return [ + for (final entry in value) + if (entry is Map) + Map.from(entry) + else + throw FormatException('its $name hold something other than objects'), + ]; } diff --git a/lib/collab/cloud_payload_upgrade.dart b/lib/collab/cloud_payload_upgrade.dart index 0ef97cdd..cedbd421 100644 --- a/lib/collab/cloud_payload_upgrade.dart +++ b/lib/collab/cloud_payload_upgrade.dart @@ -13,7 +13,7 @@ import 'package:icarus/migrations/paranoia_range_migration.dart'; /// rewritten on the server, so every client reads it the same way until /// someone edits it. /// -/// Lineup rows are never corrected. The one-row lineup (protocol 5) came +/// Lineup rows are never corrected. The lineup group row (protocol 5) came /// after data version 104, so every lineup row holds its Paranoia at the /// in-game size already; correcting one would move it twice. const paranoiaCloudPayloadVersion = 2; diff --git a/lib/collab/collab_models.dart b/lib/collab/collab_models.dart index dbcd9fb8..b91ff044 100644 --- a/lib/collab/collab_models.dart +++ b/lib/collab/collab_models.dart @@ -68,14 +68,15 @@ CloudPayload cloudElementPayload({ }; } -/// The payload kind of a lineup row (see cloud_lineup_rows.dart). -const cloudLineupPayloadKind = 'lineup'; +/// The payload kind of a lineup group row (see cloud_lineup_rows.dart). +const cloudLineupsPayloadKind = 'lineups'; -/// One lineup row's payload. Lineup rows are never corrected on read (see -/// cloud_payload_upgrade.dart), so they all stay at the current version. -CloudPayload cloudLineupPayload(Map data) { +/// One lineup group row's payload. Lineup rows are never corrected on read +/// (see cloud_payload_upgrade.dart), so they all stay at the current +/// version. +CloudPayload cloudLineupsPayload(Map data) { return { - 'kind': cloudLineupPayloadKind, + 'kind': cloudLineupsPayloadKind, 'payloadVersion': currentCloudPayloadVersion, 'data': _normalizeCloudPayloadData(data), }; diff --git a/lib/collab/convex_payload_codecs.dart b/lib/collab/convex_payload_codecs.dart index 53997697..4d28deac 100644 --- a/lib/collab/convex_payload_codecs.dart +++ b/lib/collab/convex_payload_codecs.dart @@ -111,15 +111,16 @@ final class UtilityConvexCodec implements ConvexPayloadCodec { ConvexValue encode(CloudPayload value) => _encodePayload(value, 'utility'); } -@ConvexPayload('lineup') -final class LineupConvexCodec implements ConvexPayloadCodec { - const LineupConvexCodec(); +// One lineup group: the lineups on a page joined through shared spots. +@ConvexPayload('lineups') +final class LineupsConvexCodec implements ConvexPayloadCodec { + const LineupsConvexCodec(); @override - CloudPayload decode(ConvexValue value) => _decodePayload(value, 'lineup'); + CloudPayload decode(ConvexValue value) => _decodePayload(value, 'lineups'); @override - ConvexValue encode(CloudPayload value) => _encodePayload(value, 'lineup'); + ConvexValue encode(CloudPayload value) => _encodePayload(value, 'lineups'); } // The origin, landing and link rows of protocol 4. The server still takes diff --git a/lib/collab/generated/convex_models.dart b/lib/collab/generated/convex_models.dart index dbc737f8..e5c8d9a4 100644 --- a/lib/collab/generated/convex_models.dart +++ b/lib/collab/generated/convex_models.dart @@ -1287,7 +1287,7 @@ final class LineupsListForPageResultItem { '$path.pagePublicId', ), payload: _decodePayload( - () => const LineupConvexCodec().decode( + () => const LineupsConvexCodec().decode( object.value['payload'] ?? _missing(path, 'payload'), ), '$path.payload', @@ -1321,7 +1321,7 @@ final class LineupsListForPageResultItem { 'deleted': ConvexBoolean(deleted), 'pagePublicId': ConvexString(pagePublicId), 'payload': _encodePayload( - () => const LineupConvexCodec().encode(payload), + () => const LineupsConvexCodec().encode(payload), '$path.payload', ), 'publicId': ConvexString(publicId), @@ -2861,7 +2861,7 @@ final class OpsApplyBatchResultResultsItemRejectedCurrentLineup '$path.revision', ), value: _decodePayload( - () => const LineupConvexCodec().decode( + () => const LineupsConvexCodec().decode( object.value['value'] ?? _missing(path, 'value'), ), '$path.value', @@ -2875,7 +2875,7 @@ final class OpsApplyBatchResultResultsItemRejectedCurrentLineup 'type': ConvexString('lineup'), 'revision': _encodeNumber(revision, '$path.revision'), 'value': _encodePayload( - () => const LineupConvexCodec().encode(value), + () => const LineupsConvexCodec().encode(value), '$path.value', ), }); @@ -4616,8 +4616,8 @@ CloudPayload _decodeOpsApplyBatchArgsOpsItemLineupAddPayload( '$path.kind', ); return switch (tag) { - 'lineup' => _decodePayload( - () => const LineupConvexCodec().decode(value), + 'lineups' => _decodePayload( + () => const LineupsConvexCodec().decode(value), path, ), 'lineupOrigin' => _decodePayload( @@ -4645,8 +4645,8 @@ ConvexValue _encodeOpsApplyBatchArgsOpsItemLineupAddPayload( ) { final tag = value['kind']; return switch (tag) { - 'lineup' => _encodePayload( - () => const LineupConvexCodec().encode(value), + 'lineups' => _encodePayload( + () => const LineupsConvexCodec().encode(value), path, ), 'lineupOrigin' => _encodePayload( @@ -4678,8 +4678,8 @@ CloudPayload _decodeOpsApplyBatchArgsOpsItemLineupPatchPayload( '$path.kind', ); return switch (tag) { - 'lineup' => _decodePayload( - () => const LineupConvexCodec().decode(value), + 'lineups' => _decodePayload( + () => const LineupsConvexCodec().decode(value), path, ), 'lineupOrigin' => _decodePayload( @@ -4707,8 +4707,8 @@ ConvexValue _encodeOpsApplyBatchArgsOpsItemLineupPatchPayload( ) { final tag = value['kind']; return switch (tag) { - 'lineup' => _encodePayload( - () => const LineupConvexCodec().encode(value), + 'lineups' => _encodePayload( + () => const LineupsConvexCodec().encode(value), path, ), 'lineupOrigin' => _encodePayload( diff --git a/lib/const/line_provider.dart b/lib/const/line_provider.dart index dd8b1592..3df9ada4 100644 --- a/lib/const/line_provider.dart +++ b/lib/const/line_provider.dart @@ -1448,6 +1448,19 @@ class LineUpProvider extends Notifier { ); } + /// Puts [graph]'s lineups on the canvas beside the ones there: the user's + /// version of a conflicting lineup group, kept as a copy (see + /// forkLineUpGraph). A recovery step rather than an edit, so it is not + /// undoable, as taking the cloud's version is not. + void addRecovered(LineUpGraph graph) { + final copy = graph.deepCopy(); + state = state.copyWith( + origins: [...state.origins, ...copy.origins], + landings: [...state.landings, ...copy.landings], + links: [...state.links, ...copy.links], + ); + } + static String objectToJson(LineUpGraph graph) { return jsonEncode(graph.toJson()); } diff --git a/lib/providers/collab/active_page_live_sync_provider.dart b/lib/providers/collab/active_page_live_sync_provider.dart index 0c2a21ee..ead7b12f 100644 --- a/lib/providers/collab/active_page_live_sync_provider.dart +++ b/lib/providers/collab/active_page_live_sync_provider.dart @@ -89,17 +89,32 @@ class ActivePageLiveSyncNotifier extends Notifier { /// hold ends, but no longer ordered against anything on the server. final Set _heldDeletedKeys = {}; - /// Every alias drawing lineups has given a spot (see - /// lineUpGraphFromCloudRows), so rows are written under the real ids. An - /// alias names its real id and the lineup it came from, so one alias - /// always means the same spot and they are only ever added. - CloudLineupAliases _lineupAliases = CloudLineupAliases.none; - - /// Records the aliases a canvas about to be drawn from cloud rows uses. - void noteLineupAliases(CloudLineupAliases aliases) { - _lineupAliases = _lineupAliases.followedBy(aliases); + /// Per page, the lineup group each lineup and spot was last drawn from or + /// written to (see cloudLineupRows), so a group keeps its id and its + /// lineups while the canvas changes under it. Per page because a page + /// duplicated before cloud sync repeats its lineup ids. Never forgotten: + /// an undo can bring back a lineup long after its row moved on. + final Map> _lineupGroupOfByPage = {}; + + /// Records the groups a canvas of [pageId] about to be drawn from cloud + /// rows uses. + void noteLineupGroups(String pageId, Map groupOf) { + (_lineupGroupOfByPage[pageId] ??= {}).addAll(groupOf); } + /// The server's version of [key] the canvas was drawn from, which local + /// edits to it were made against: its payload, or null when the canvas was + /// drawn without it or with it deleted. + Object? hydratedBasePayload(EntitySyncKey key) { + final base = _hydratedBaseByEntityKey[key]; + return base == null || base.deleted ? null : base.payload; + } + + /// The lineup group [itemId] (a lineup, origin or landing) on [pageId] + /// was last in. + String? lineupGroupOf(String pageId, String itemId) => + _lineupGroupOfByPage[pageId]?[itemId]; + @override ActivePageLiveSyncState build() { return const ActivePageLiveSyncState(); @@ -565,12 +580,14 @@ class ActivePageLiveSyncNotifier extends Notifier { } // The user deleted, on the canvas, an item whose change the server - // refused: the server has it deleted too, so the refused change has - // nothing left to keep, and Keep mine must not bring the item back. + // refused because a teammate deleted it: both sides have it deleted, + // so the refused change has nothing left to keep, and Keep mine must + // not bring the item back. if (local == null && existingOverlay != null && (remote == null || remote.deleted) && - queueState.attentionByEntityKey.containsKey(key)) { + queueState.attentionByEntityKey.containsKey(key) && + ref.read(strategyOpQueueProvider.notifier).refusedAsDeleted(key)) { settledAttention.add(key); } @@ -999,15 +1016,26 @@ class ActivePageLiveSyncNotifier extends Notifier { ); } - // One row per lineup, carrying its origin and landing as the canvas - // draws them, under their real ids. A change to a shared spot changes - // every lineup drawn on it; a lineup nobody changed matches its stored - // row, so it is never written. + // One row per lineup group, holding its lineups and their spots as the + // canvas draws them. A group nobody changed matches its stored row, so + // it is never written. A new group never takes the id of a row this page + // has or had, so it never lands on a teammate's group or a deleted one. final freshLineupSortIndex = freshSortIndexes(EntitySyncKeyKind.lineup); - for (final row in cloudLineupRows( + final lineupGroupOf = _lineupGroupOfByPage[pageId] ??= {}; + final lineupRows = cloudLineupRows( ref.read(lineUpProvider).graph, - aliases: _lineupAliases, - )) { + groupOf: lineupGroupOf, + takenGroupIds: { + for (final key in [ + ..._hydratedBaseByEntityKey.keys, + ...state.overlayByEntityKey.keys, + ]) + if (key.pageId == pageId && key.kind == EntitySyncKeyKind.lineup) + key.entityId!, + }, + ); + lineupGroupOf.addAll(lineupRows.groupOf); + for (final row in lineupRows.rows) { final key = EntitySyncKey.lineup(pageId, row.publicId); entities[key] = _NormalizedEntity( key: key, diff --git a/lib/providers/collab/lineup_conflicts_provider.dart b/lib/providers/collab/lineup_conflicts_provider.dart new file mode 100644 index 00000000..61fc0b04 --- /dev/null +++ b/lib/providers/collab/lineup_conflicts_provider.dart @@ -0,0 +1,95 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:icarus/collab/cloud_lineup_rows.dart'; +import 'package:icarus/collab/collab_models.dart'; +import 'package:icarus/const/line_provider.dart'; +import 'package:icarus/providers/collab/active_page_live_sync_models.dart'; +import 'package:icarus/providers/collab/active_page_live_sync_provider.dart'; +import 'package:icarus/providers/collab/remote_strategy_snapshot_provider.dart'; +import 'package:icarus/providers/collab/strategy_op_queue_provider.dart'; +import 'package:icarus/strategy/lineup_group_changes.dart'; + +/// A lineup group whose change the server refused, and what each side did +/// to it since the version both started from. +class LineupGroupConflict { + const LineupGroupConflict({ + required this.key, + required this.yours, + required this.cloud, + }); + + final EntitySyncKey key; + + /// The user's waiting version, against the version it was made from. + final List yours; + + /// The cloud's version, against the same one. + final List cloud; +} + +/// What the active strategy's refused work does to its lineup groups, when +/// that work is all lineup groups on the page on screen: the case Keep both +/// covers. Null otherwise, and the sync popover offers Keep mine and Use +/// cloud alone. +/// +/// Every version is read from what this device already has: the user's +/// newest waiting change, the server's version the canvas was drawn from, +/// and the live copy of the page. When one of them cannot be read, there is +/// nothing trustworthy to show, so this is null too. +final lineupConflictsProvider = Provider?>((ref) { + final queue = ref.watch(strategyOpQueueProvider); + final attention = queue.attentionByEntityKey; + final page = ref.watch(remoteEditorSnapshotProvider).valueOrNull?.activePage; + if (attention.isEmpty || page == null) return null; + final pageId = page.page.publicId; + if (attention.keys.any( + (key) => key.kind != EntitySyncKeyKind.lineup || key.pageId != pageId, + )) { + return null; + } + + final liveSync = ref.read(activePageLiveSyncProvider.notifier); + final cloudRows = {for (final row in page.lineups) row.publicId: row}; + final conflicts = []; + for (final MapEntry(:key, value: refused) in attention.entries) { + final basePayload = liveSync.hydratedBasePayload(key); + final newest = (queue.successorByEntityKey[key] ?? refused).pending.op; + final yoursPayload = switch (newest) { + LineupAddOp(:final payload) => payload, + LineupPatchOp(:final payload?) => payload, + LineupDeleteOp() => null, + _ => basePayload, + }; + final cloudRow = cloudRows[key.entityId]; + final base = _groupGraph(key, basePayload); + final yours = _groupGraph(key, yoursPayload); + final cloud = _groupGraph( + key, + cloudRow == null || cloudRow.deleted ? null : cloudRow.payload, + ); + if (base == null || yours == null || cloud == null) return null; + conflicts.add( + LineupGroupConflict( + key: key, + yours: lineupChanges(from: base, to: yours), + cloud: lineupChanges(from: base, to: cloud), + ), + ); + } + return conflicts; +}); + +/// The lineups one group row's [payload] holds; empty for no row, null when +/// the row cannot be read. +LineUpGraph? _groupGraph(EntitySyncKey key, Object? payload) { + if (payload == null) return LineUpGraph.empty; + try { + return lineUpGraphFromCloudRows([ + CloudLineupRow( + publicId: key.entityId!, + payload: cloudObjectPayload(payload), + ), + ]).graph; + } on FormatException { + return null; + } +} diff --git a/lib/providers/collab/strategy_op_queue_provider.dart b/lib/providers/collab/strategy_op_queue_provider.dart index 57a81186..8ab256a3 100644 --- a/lib/providers/collab/strategy_op_queue_provider.dart +++ b/lib/providers/collab/strategy_op_queue_provider.dart @@ -1128,13 +1128,27 @@ class StrategyOpQueueNotifier extends Notifier { ) => _dropAttention(entityKeys, adoptRemote: true); + /// Whether the server refused [key]'s waiting work because a teammate + /// deleted what it edits (rejection reason `deleted`). + bool refusedAsDeleted(EntitySyncKey key) => + _recordForActiveKey(key)?.lastError == OpRejectionReason.deleted.wireName; + /// Drops the refused work of entities the user has since deleted on the - /// canvas while the server has them deleted too. Both sides agree they - /// are gone, so nothing is left to keep, and Keep mine must not bring - /// them back. Unlike [discardRejected] the canvas is already the truth: - /// there is nothing to adopt. + /// canvas, when the server refused it because a teammate deleted them + /// ([refusedAsDeleted]). Both sides agree they are gone, so nothing is + /// left to keep, and Keep mine must not bring them back. Work refused for + /// any other reason stays: only that refusal says the server deleted the + /// item, rather than never having it or holding it elsewhere. Unlike + /// [discardRejected] the canvas is already the truth: there is nothing to + /// adopt. Future> settleAttention(Set entityKeys) => - _dropAttention(entityKeys, adoptRemote: false); + _dropAttention( + { + for (final key in entityKeys) + if (refusedAsDeleted(key)) key + }, + adoptRemote: false, + ); Future> _dropAttention( Set entityKeys, { diff --git a/lib/providers/strategy_page_session_provider.dart b/lib/providers/strategy_page_session_provider.dart index 084108c1..a6e842f0 100644 --- a/lib/providers/strategy_page_session_provider.dart +++ b/lib/providers/strategy_page_session_provider.dart @@ -6,7 +6,9 @@ import 'package:flutter/foundation.dart' show listEquals; import 'package:flutter/widgets.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:hive_ce/hive.dart'; +import 'package:icarus/collab/cloud_lineup_rows.dart'; import 'package:icarus/collab/collab_models.dart'; +import 'package:icarus/const/line_provider.dart'; import 'package:icarus/collab/convex_strategy_repository.dart'; import 'package:icarus/const/coordinate_system.dart'; import 'package:icarus/const/maps.dart'; @@ -41,6 +43,7 @@ import 'package:icarus/providers/navigation_geometry_provider.dart'; import 'package:icarus/providers/view_cone_geometry_provider.dart'; import 'package:icarus/services/app_error_reporter.dart'; import 'package:icarus/strategy/strategy_page_apply.dart'; +import 'package:icarus/strategy/lineup_group_changes.dart'; import 'package:icarus/strategy/strategy_page_models.dart'; import 'package:icarus/strategy/strategy_page_source.dart'; import 'package:icarus/view_cone/vision_geometry.dart'; @@ -57,6 +60,19 @@ enum PageSwitchDirection { next, previous } /// never got. [name] is its name when it was last loaded. typedef DeletedPage = ({String pageId, String name}); +/// How Keep both ended (see StrategyPageSessionNotifier.keepBothForRejected). +enum KeepBothOutcome { + /// The cloud's versions stand, and the user's are beside them as copies. + kept, + + /// The cloud's versions could not be loaded. Nothing changed. + unchanged, + + /// The user's versions were added as copies, but the cloud's could not be + /// loaded after all, so the conflicts still wait. Use cloud finishes. + copiesOnly, +} + /// How an attempt to restore a deleted page ended. enum DeletedPageRestore { /// The page is back on the server, and the work on it is saving as usual. @@ -544,6 +560,60 @@ class StrategyPageSessionNotifier extends Notifier { } } + /// Keeps both versions of every conflicting lineup group: the cloud's + /// stays the group, and the user's goes beside it as a copy under fresh + /// ids (see forkLineUpGraph), so neither side's work is lost. A version + /// that deletes the group, or only reorders it, holds nothing to copy. + /// + /// Only for conflicts that are all lineup groups on the page on screen + /// (see lineupConflictsProvider); anything else is a [StateError]. The + /// cloud's version is loaded first, so a device that cannot reach it + /// changes nothing. The copies then go on the canvas, and + /// [useCloudVersionsForRejected] queues them before it discards anything: + /// if taking the cloud's version still fails, the copies and the waiting + /// versions both remain, and Use cloud finishes the job. + Future keepBothForRejected() async { + final strategyState = ref.read(strategyProvider); + final queue = ref.read(strategyOpQueueProvider); + final attention = queue.attentionByEntityKey; + final activePageId = state.activePageId; + if (strategyState.source != StrategySource.cloud || + attention.isEmpty || + attention.keys.any((key) => + key.kind != EntitySyncKeyKind.lineup || + key.pageId != activePageId)) { + throw StateError('Keep both is only for lineup groups on this page'); + } + final copies = []; + for (final MapEntry(:key, value: refused) in attention.entries) { + final newest = (queue.successorByEntityKey[key] ?? refused).pending.op; + final payload = switch (newest) { + LineupAddOp(:final payload) => payload, + LineupPatchOp(:final payload?) => payload, + _ => null, + }; + if (payload == null) continue; + copies.add(forkLineUpGraph(lineUpGraphFromCloudRows([ + CloudLineupRow(publicId: key.entityId!, payload: payload), + ]).graph)); + } + + await ref.read(remoteEditorSnapshotProvider.notifier).refresh(); + final snapshot = ref.read(remoteEditorSnapshotProvider).valueOrNull; + if (snapshot == null || + snapshot.header.publicId != strategyState.strategyId) { + return KeepBothOutcome.unchanged; + } + + final lineUps = ref.read(lineUpProvider.notifier); + for (final copy in copies) { + lineUps.addRecovered(copy); + } + return await useCloudVersionsForRejected() + ? KeepBothOutcome.kept + : KeepBothOutcome.copiesOnly; + } + /// Adopts the cloud version for every current conflict in this strategy. /// /// The authoritative page is loaded before any local intent is discarded. diff --git a/lib/strategy/lineup_group_changes.dart b/lib/strategy/lineup_group_changes.dart new file mode 100644 index 00000000..12f8ddc8 --- /dev/null +++ b/lib/strategy/lineup_group_changes.dart @@ -0,0 +1,136 @@ +import 'dart:convert'; + +import 'package:icarus/collab/canonical_json.dart'; +import 'package:icarus/const/agents.dart'; +import 'package:icarus/const/line_provider.dart'; +import 'package:uuid/uuid.dart'; + +/// What happened to one lineup between two versions of its group. +class LineupChange { + const LineupChange({required this.label, required this.description}); + + /// The lineup as the user knows it: its name, or its agent's. + final String label; + + /// What changed, e.g. "added" or "notes edited, landing moved". + final String description; +} + +/// The lineups that differ between [from] and [to], one entry each, in +/// [to]'s order and then the lineups only [from] has. +List lineupChanges({ + required LineUpGraph from, + required LineUpGraph to, +}) { + final before = _Lookup(from); + final after = _Lookup(to); + final changes = []; + for (final link in to.links) { + final old = before.links[link.id]; + if (old == null) { + changes.add(LineupChange(label: after.label(link), description: 'added')); + continue; + } + final what = [ + if (old.name != link.name) 'renamed', + if (old.notes != link.notes) 'notes edited', + if (old.youtubeLink != link.youtubeLink) 'video changed', + if (!_sameJson( + [for (final image in old.images) image.toJson()], + [for (final image in link.images) image.toJson()], + )) + 'images changed', + if (!_sameJson( + before.origins[old.originId]?.toJson(), + after.origins[link.originId]?.toJson(), + )) + 'standing spot moved', + if (!_sameJson( + before.landings[old.landingId]?.toJson(), + after.landings[link.landingId]?.toJson(), + )) + 'landing moved', + ]; + if (what.isNotEmpty) { + changes.add( + LineupChange(label: after.label(link), description: what.join(', ')), + ); + } + } + for (final link in from.links) { + if (!after.links.containsKey(link.id)) { + changes.add( + LineupChange(label: before.label(link), description: 'deleted'), + ); + } + } + return changes; +} + +/// A copy of [graph] under fresh ids, for keeping it beside the version it +/// conflicted with: its lineups, spots, and the agent and ability on each +/// spot are all new, so nothing in it is the original. Images stay the same +/// images. +LineUpGraph forkLineUpGraph(LineUpGraph graph) { + const uuid = Uuid(); + final originIds = {for (final origin in graph.origins) origin.id: uuid.v4()}; + final landingIds = { + for (final landing in graph.landings) landing.id: uuid.v4(), + }; + return LineUpGraph( + origins: [ + for (final origin in graph.origins) + LineUpOrigin( + id: originIds[origin.id]!, + agent: origin.agent.copyWith( + id: uuid.v4(), + lineUpID: originIds[origin.id], + )..isDeleted = origin.agent.isDeleted, + ), + ], + landings: [ + for (final landing in graph.landings) + LineUpLanding( + id: landingIds[landing.id]!, + ability: landing.ability.copyWith( + id: uuid.v4(), + lineUpID: landingIds[landing.id], + )..isDeleted = landing.ability.isDeleted, + ), + ], + links: [ + for (final link in graph.links) + if ((originIds[link.originId], landingIds[link.landingId]) + case (final originId?, final landingId?)) + link.copyWith( + id: uuid.v4(), + originId: originId, + landingId: landingId, + ), + ], + ); +} + +/// Model `toJson()` output can nest objects with their own `toJson`, so it +/// goes through a plain JSON round trip before the canonical comparison. +bool _sameJson(Object? a, Object? b) => + canonicalCloudJsonEncode(jsonDecode(jsonEncode(a))) == + canonicalCloudJsonEncode(jsonDecode(jsonEncode(b))); + +class _Lookup { + _Lookup(LineUpGraph graph) + : origins = {for (final origin in graph.origins) origin.id: origin}, + landings = {for (final landing in graph.landings) landing.id: landing}, + links = {for (final link in graph.links) link.id: link}; + + final Map origins; + final Map landings; + final Map links; + + String label(LineUpLink link) { + final name = link.name.trim(); + if (name.isNotEmpty) return name; + final agent = AgentData.agents[origins[link.originId]?.agent.type]?.name; + return agent == null ? 'Unnamed lineup' : '$agent lineup'; + } +} diff --git a/lib/strategy/remote_page_merge.dart b/lib/strategy/remote_page_merge.dart index bbd82c44..c11d1b67 100644 --- a/lib/strategy/remote_page_merge.dart +++ b/lib/strategy/remote_page_merge.dart @@ -34,36 +34,46 @@ List mergeRemoteItems({ /// The server's lineups ([remote]), except the ones the user is holding: /// those stay as the canvas ([local]) has them, as [mergeRemoteItems] keeps -/// held items. Returns the merged graph and the ids of the held lineups. +/// held items. Returns the merged graph and the lineup groups held. /// -/// A lineup is held when the user holds it, its origin or its landing, or -/// when it shares a spot with a held lineup: a shared spot is drawn once, -/// so every lineup on it takes the same side. The other lineups take the -/// server's copy. A held lineup only the server has (a teammate's new -/// lineup on a held spot) waits off the canvas until the hold ends, like a -/// held lineup the server removed waits on it. +/// The user holds a lineup group by holding one of its lineups or spots; +/// [groupOf] names the group of a lineup or spot, local or remote. A group +/// is one cloud row, so it takes one side whole: every lineup in a held +/// group, and every lineup sharing a spot with one, stays as the canvas has +/// it. The other lineups take the server's copy. A held lineup only the +/// server has (a teammate's new lineup in a held group) waits off the +/// canvas until the hold ends, like a held lineup the server removed waits +/// on it. (LineUpGraph, Set) mergeHeldLineups({ required LineUpGraph local, required LineUpGraph remote, required Set holding, + required String? Function(String id) groupOf, }) { final allLinks = [...local.links, ...remote.links]; final held = {}; final heldIds = {...holding}; + final heldGroups = { + for (final id in holding) + if (groupOf(id) case final group?) group, + }; for (var grew = true; grew;) { grew = false; for (final link in allLinks) { if (held.contains(link.id)) continue; + final group = groupOf(link.id); if (heldIds.contains(link.id) || heldIds.contains(link.originId) || - heldIds.contains(link.landingId)) { + heldIds.contains(link.landingId) || + heldGroups.contains(group)) { held.add(link.id); heldIds.addAll([link.originId, link.landingId]); + if (group != null) heldGroups.add(group); grew = true; } } } - if (held.isEmpty) return (remote, held); + if (held.isEmpty) return (remote, heldGroups); final localLinks = {for (final link in local.links) link.id: link}; final remoteLinkIds = {for (final link in remote.links) link.id}; @@ -104,6 +114,6 @@ List mergeRemoteItems({ ], links: links, ), - held, + heldGroups, ); } diff --git a/lib/strategy/strategy_cloud_migration.dart b/lib/strategy/strategy_cloud_migration.dart index c9e72b81..e225702e 100644 --- a/lib/strategy/strategy_cloud_migration.dart +++ b/lib/strategy/strategy_cloud_migration.dart @@ -3,7 +3,6 @@ import 'dart:convert'; import 'package:icarus/collab/collab_models.dart'; import 'package:icarus/collab/cloud_lineup_rows.dart'; import 'package:icarus/collab/cloud_media_models.dart'; -import 'package:icarus/const/line_provider.dart'; import 'package:icarus/providers/drawing_provider.dart'; import 'package:icarus/providers/strategy_page.dart'; import 'package:uuid/uuid.dart'; @@ -74,20 +73,15 @@ void appendMigratedPageOps( buildMigratedElementOp(page.id, elementId, payload, elementOrder++)); } - // One row per lineup, keyed by its id. A lineup whose id another page - // already took (a page duplicated before cloud sync) gets a fresh one. Its - // origin and landing keep their ids: spots are shared within a page only. - final graph = page.lineUpGraph; + // One row per lineup group. A group's id must be unique in the strategy, + // so one whose id another page already took (a page duplicated before + // cloud sync repeats its lineup ids) takes another of its lineup ids. + // Ids inside a row only need to be unique in the row, so they stay. final rows = cloudLineupRows( - LineUpGraph( - origins: graph.origins, - landings: graph.landings, - links: [ - for (final link in graph.links) - link.copyWith(id: nextUniqueMigrationId(link.id, usedLineupIds)), - ], - ), - ); + page.lineUpGraph, + takenGroupIds: usedLineupIds, + ).rows; + usedLineupIds.addAll([for (final row in rows) row.publicId]); var lineupOrder = 0; for (final row in rows) { ops.add( diff --git a/lib/strategy/strategy_page_apply.dart b/lib/strategy/strategy_page_apply.dart index 9ae321a7..5fc118f6 100644 --- a/lib/strategy/strategy_page_apply.dart +++ b/lib/strategy/strategy_page_apply.dart @@ -14,6 +14,7 @@ import 'package:icarus/providers/text_provider.dart'; import 'package:icarus/providers/utility_provider.dart'; import 'package:icarus/const/line_provider.dart'; import 'package:icarus/providers/collab/active_page_live_sync_models.dart'; +import 'package:icarus/providers/collab/active_page_live_sync_provider.dart'; import 'package:icarus/strategy/remote_page_merge.dart'; import 'package:icarus/strategy/strategy_page_models.dart'; @@ -92,16 +93,18 @@ Set mergeRemoteStrategyEditorPageData( ref.read(placedImageProvider.notifier).mergeRemote(data.images, keep); ref.read(utilityProvider.notifier).mergeRemote(data.utilities, keep); - final (lineUpGraph, heldLineups) = mergeHeldLineups( + final liveSync = ref.read(activePageLiveSyncProvider.notifier); + final (lineUpGraph, heldGroups) = mergeHeldLineups( local: ref.read(lineUpProvider).graph, remote: data.lineUpGraph.deepCopy(), holding: holding, + groupOf: (id) => liveSync.lineupGroupOf(data.pageId, id), ); ref.read(lineUpProvider.notifier).mergeRemote(lineUpGraph); heldBack.addAll({ for (final key in changed) if (key.kind == EntitySyncKeyKind.lineup && - heldLineups.contains(key.entityId)) + heldGroups.contains(key.entityId)) key, }); diff --git a/lib/strategy/strategy_page_source.dart b/lib/strategy/strategy_page_source.dart index 949626cf..3cb26005 100644 --- a/lib/strategy/strategy_page_source.dart +++ b/lib/strategy/strategy_page_source.dart @@ -286,7 +286,7 @@ class CloudStrategyPageSource implements StrategyPageSource { ]); ref .read(activePageLiveSyncProvider.notifier) - .noteLineupAliases(cloudLineups.aliases); + .noteLineupGroups(page.publicId, cloudLineups.groupOf); final mapValue = Maps.mapNames.entries.firstWhere( (entry) => entry.value == snapshot.header.mapData, @@ -461,7 +461,7 @@ class CloudStrategyPageSource implements StrategyPageSource { ]); ref .read(activePageLiveSyncProvider.notifier) - .noteLineupAliases(cloudLineups.aliases); + .noteLineupGroups(page.publicId, cloudLineups.groupOf); final mapValue = Maps.mapNames.entries.firstWhere( (entry) => entry.value == snapshot.header.mapData, diff --git a/lib/widgets/cloud_sync_button.dart b/lib/widgets/cloud_sync_button.dart index 35fa983b..9384f34c 100644 --- a/lib/widgets/cloud_sync_button.dart +++ b/lib/widgets/cloud_sync_button.dart @@ -8,12 +8,14 @@ import 'package:icarus/const/settings.dart'; import 'package:icarus/providers/collab/client_upgrade_required_provider.dart'; import 'package:icarus/providers/collab/cloud_media_upload_queue_provider.dart'; import 'package:icarus/providers/collab/cloud_sync_status_provider.dart'; +import 'package:icarus/providers/collab/lineup_conflicts_provider.dart'; import 'package:icarus/providers/collab/strategy_capabilities_provider.dart'; import 'package:icarus/providers/collab/strategy_conflict_provider.dart'; import 'package:icarus/providers/collab/strategy_op_queue_provider.dart'; import 'package:icarus/providers/strategy_page_session_provider.dart'; import 'package:icarus/providers/strategy_provider.dart'; import 'package:icarus/providers/strategy_save_state_provider.dart'; +import 'package:icarus/strategy/lineup_group_changes.dart'; import 'package:icarus/strategy/strategy_page_models.dart'; import 'package:icarus/widgets/client_upgrade_button.dart'; import 'package:icarus/widgets/dialogs/deleted_page_dialog.dart'; @@ -189,6 +191,46 @@ class _CloudSyncButtonState extends ConsumerState { } } + Future _keepBoth() async { + if (_isResolving) return; + setState(() { + _isResolving = true; + _resolutionError = null; + }); + String? resolutionError; + try { + switch (await ref + .read(strategyPageSessionProvider.notifier) + .keepBothForRejected()) { + case KeepBothOutcome.kept: + _popoverController.hide(); + case KeepBothOutcome.unchanged: + resolutionError = 'Could not load the cloud version. ' + 'Nothing was changed.'; + case KeepBothOutcome.copiesOnly: + resolutionError = 'Your version was added as a copy, but the ' + 'cloud version could not be loaded. Choose Use cloud to ' + 'finish.'; + } + } catch (error, stackTrace) { + log( + 'Failed to keep both versions: $error', + name: 'cloud_conflict_resolution', + error: error, + stackTrace: stackTrace, + ); + resolutionError = 'Could not keep both versions. ' + 'Your version is still saved on this device.'; + } finally { + if (mounted) { + setState(() { + _isResolving = false; + _resolutionError = resolutionError; + }); + } + } + } + @override Widget build(BuildContext context) { final source = ref.watch(strategyProvider.select((state) => state.source)); @@ -263,6 +305,7 @@ class _CloudSyncButtonState extends ConsumerState { upgradeRequired: upgradeRequired, saveState: saveState, rejectedCount: opQueueState.attentionByEntityKey.length, + lineupConflicts: ref.watch(lineupConflictsProvider), hasOtherStrategyWork: hasOtherStrategyWork, hasOtherStrategyAttention: hasOtherStrategyAttention, hasActiveStrategyAttention: hasActiveStrategyAttention, @@ -270,6 +313,7 @@ class _CloudSyncButtonState extends ConsumerState { resolutionError: _resolutionError, onRetry: _retry, onUseCloudVersions: _useCloudVersions, + onKeepBoth: _keepBoth, ), child: EditorToolbarButton( key: ValueKey('cloud-sync-button-${status.name}'), @@ -361,6 +405,7 @@ class _SyncStatusPopover extends StatelessWidget { required this.upgradeRequired, required this.saveState, required this.rejectedCount, + required this.lineupConflicts, required this.hasOtherStrategyWork, required this.hasOtherStrategyAttention, required this.hasActiveStrategyAttention, @@ -368,6 +413,7 @@ class _SyncStatusPopover extends StatelessWidget { required this.resolutionError, required this.onRetry, required this.onUseCloudVersions, + required this.onKeepBoth, }); final _SyncStatus status; @@ -377,6 +423,11 @@ class _SyncStatusPopover extends StatelessWidget { final bool upgradeRequired; final StrategySaveState saveState; final int rejectedCount; + + /// What each side did to the lineup groups in conflict, when the refused + /// work is all lineup groups on this page (see lineupConflictsProvider). + /// Then the popover lists the changes and offers Keep both. + final List? lineupConflicts; final bool hasOtherStrategyWork; final bool hasOtherStrategyAttention; final bool hasActiveStrategyAttention; @@ -384,9 +435,18 @@ class _SyncStatusPopover extends StatelessWidget { final String? resolutionError; final Future Function() onRetry; final Future Function() onUseCloudVersions; + final Future Function() onKeepBoth; bool get hasRejectedWork => rejectedCount > 0; + /// The lineup changes to list, when there is no more specific reason to + /// show instead (a deleted page, an oversized change). + List? get _shownLineupConflicts { + final error = saveState.cloudSyncError; + if (error != null && isSpecificAttentionReason(error)) return null; + return lineupConflicts; + } + @override Widget build(BuildContext context) { if (upgradeRequired) { @@ -418,6 +478,13 @@ class _SyncStatusPopover extends StatelessWidget { onPressed: isResolving ? null : onUseCloudVersions, child: const Text('Use cloud'), ), + if (_shownLineupConflicts != null) + ShadButton.secondary( + size: ShadButtonSize.sm, + expands: false, + onPressed: isResolving ? null : onKeepBoth, + child: const Text('Keep both'), + ), ShadButton( size: ShadButtonSize.sm, expands: false, @@ -464,6 +531,8 @@ class _SyncStatusPopover extends StatelessWidget { height: 1.35, ), ), + if (_shownLineupConflicts case final conflicts?) + _LineupConflictList(conflicts: conflicts), if (resolutionError != null) ...[ const SizedBox(height: 8), Text( @@ -542,7 +611,16 @@ class _SyncStatusPopover extends StatelessWidget { final parts = []; final error = saveState.cloudSyncError; final hasSpecificReason = error != null && isSpecificAttentionReason(error); - if (hasRejectedWork && !hasSpecificReason) { + if (_shownLineupConflicts != null) { + parts.add( + 'A teammate changed these lineups while you were working on them. ' + 'Your version remains saved on this device.', + ); + parts.add( + 'Keep mine replaces their changes, Use cloud replaces yours, and ' + 'Keep both adds yours beside theirs as a copy.', + ); + } else if (hasRejectedWork && !hasSpecificReason) { parts.add( 'Another edit reached the cloud first. Your version remains saved ' 'on this device.', @@ -592,3 +670,80 @@ class _SyncStatusPopover extends StatelessWidget { return '$hour:$minute'; } } + +/// The lineups each side changed in the conflicting lineup groups: what +/// Keep mine would replace (the cloud's changes) and what Use cloud would +/// (the user's), so neither choice drops work the user cannot see. +class _LineupConflictList extends StatelessWidget { + const _LineupConflictList({required this.conflicts}); + + final List conflicts; + + /// More lines than this read as a wall; the rest are counted instead. + static const _maxLines = 4; + + @override + Widget build(BuildContext context) { + final yours = [for (final conflict in conflicts) ...conflict.yours]; + final cloud = [for (final conflict in conflicts) ...conflict.cloud]; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 10), + _section(context, 'Your changes', yours), + const SizedBox(height: 8), + _section(context, 'Cloud changes', cloud), + ], + ); + } + + Widget _section( + BuildContext context, + String title, + List changes, + ) { + final theme = ShadTheme.of(context); + final lineStyle = theme.textTheme.small.copyWith( + color: theme.colorScheme.foreground, + fontSize: 12, + height: 1.35, + ); + final mutedStyle = lineStyle.copyWith( + color: theme.colorScheme.mutedForeground, + ); + final shown = changes.take(_maxLines).toList(); + final hidden = changes.length - shown.length; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: theme.textTheme.small.copyWith( + color: theme.colorScheme.mutedForeground, + fontSize: 12, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 4), + if (changes.isEmpty) Text('No lineup changes', style: mutedStyle), + for (final change in shown) + Text.rich( + TextSpan( + children: [ + TextSpan(text: change.label), + TextSpan(text: ' · ${change.description}', style: mutedStyle), + ], + ), + style: lineStyle, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + if (hidden > 0) + Text( + hidden == 1 ? 'and 1 more lineup' : 'and $hidden more lineups', + style: mutedStyle, + ), + ], + ); + } +} diff --git a/test/cloud_lineup_rows_test.dart b/test/cloud_lineup_rows_test.dart index 82167e0c..d8113f89 100644 --- a/test/cloud_lineup_rows_test.dart +++ b/test/cloud_lineup_rows_test.dart @@ -8,18 +8,14 @@ import 'package:icarus/const/agents.dart'; import 'package:icarus/const/line_provider.dart'; import 'package:icarus/const/placed_classes.dart'; -LineUpOrigin _origin( - String id, { - Offset position = const Offset(10, 20), - String? lineUpID, -}) => +LineUpOrigin _origin(String id, {Offset position = const Offset(10, 20)}) => LineUpOrigin( id: id, agent: PlacedAgent( id: 'agent-$id', type: AgentType.sova, position: position, - lineUpID: lineUpID ?? id, + lineUpID: id, ), ); @@ -34,11 +30,14 @@ LineUpLanding _landing(String id, {Offset position = const Offset(30, 40)}) => ), ); -/// Two origins into one landing (fan-in), and a second lineup out of -/// origin a (fan-out). -LineUpGraph _shared() => LineUpGraph( - origins: [_origin('a'), _origin('b')], - landings: [_landing('shared'), _landing('other')], +LineUpLink _link(String id, String originId, String landingId) => + LineUpLink(id: id, originId: originId, landingId: landingId, name: id); + +/// One group: two origins into landing `shared` (fan-in), and a second +/// lineup out of origin a (fan-out). And a second group, k9 alone. +LineUpGraph _page() => LineUpGraph( + origins: [_origin('a'), _origin('b'), _origin('z')], + landings: [_landing('shared'), _landing('other'), _landing('far')], links: [ LineUpLink( id: 'k1', @@ -48,389 +47,244 @@ LineUpGraph _shared() => LineUpGraph( notes: 'jump throw', images: [SimpleImageData(id: 'img-1', fileExtension: '.png')], ), - LineUpLink(id: 'k2', originId: 'b', landingId: 'shared', name: 'B'), - LineUpLink(id: 'k3', originId: 'a', landingId: 'other', name: 'C'), + _link('k2', 'b', 'shared'), + _link('k9', 'z', 'far'), + _link('k3', 'a', 'other'), ], ); -/// The row of lineup [id] from [origin] into [landing], as a client that -/// saw that copy of each wrote it. -CloudLineupRow _row(String id, LineUpOrigin origin, LineUpLanding landing) => - cloudLineupRows(LineUpGraph( - origins: [origin], - landings: [landing], - links: [ - LineUpLink(id: id, originId: origin.id, landingId: landing.id), - ], - )).single; - -const _here = Offset(30, 40); -const _moved = Offset(99, 99); -const _elsewhere = Offset(55, 55); - -/// Lineups k1, k2 and k3 into landing `shared`: k1 carries it [_here], k2 -/// and k3 a copy [_moved] that never reached k1. -List _splitLanding() => [ - _row('k1', _origin('o1'), _landing('shared')), - _row('k2', _origin('o2'), _landing('shared', position: _moved)), - _row('k3', _origin('o3'), _landing('shared', position: _moved)), - ]; - String _json(LineUpGraph graph) => jsonEncode(graph.toJson()); -/// [value] as plain JSON. -Object? _plain(Object? value) => jsonDecode(jsonEncode(value)); - /// [rows] as canonical JSON, to compare them exactly. String _canonical(Iterable rows) => - canonicalCloudJsonEncode(_plain([ + canonicalCloudJsonEncode(jsonDecode(jsonEncode([ for (final row in rows) {'publicId': row.publicId, 'payload': row.payload}, - ])); - -/// The [end] (`origin` or `landing`) [row] carries. -Map _end(CloudLineupRow row, String end) => - Map.from((row.payload['data'] as Map)[end] as Map); - -/// Each lineup's origin and landing ids as [read] draws them. -Map _ends(CloudLineups read) => { - for (final link in read.graph.links) - link.id: (link.originId, link.landingId), - }; - -Offset _originAt(CloudLineups read, String id) => - read.graph.origins.firstWhere((o) => o.id == id).agent.position; + ]))); -Offset _landingAt(CloudLineups read, String id) => - read.graph.landings.firstWhere((l) => l.id == id).ability.position; +Map _data(CloudLineupRow row) => + Map.from(row.payload['data'] as Map); -/// Every order of [items]. -List> _orders(List items) => items.length <= 1 - ? [items] - : [ - for (final (i, first) in items.indexed) - for (final rest in _orders([...items]..removeAt(i))) [first, ...rest], - ]; +List _ids(Object? entries) => [ + for (final entry in entries as List) (entry as Map)['id'] as String, + ]; void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - test('each lineup is one row, carrying its whole origin and landing', () { - final rows = cloudLineupRows(_shared()); - - expect(rows.map((row) => row.publicId), ['k1', 'k2', 'k3']); - final first = rows.first.payload; - expect(first['kind'], cloudLineupPayloadKind); - expect(first['payloadVersion'], currentCloudPayloadVersion); - final data = first['data'] as Map; - expect(data.keys.toSet(), { - 'id', - 'name', - 'youtubeLink', - 'notes', - 'images', - 'origin', - 'landing', - }); - // An end is the spot itself, nothing added. - expect(_plain(data['origin']), _plain(_origin('a').toJson())); - expect(_plain(data['landing']), _plain(_landing('shared').toJson())); - expect(data['images'], [ - {'id': 'img-1', 'fileExtension': '.png'}, - ]); - }); - - test('copies that agree draw one shared spot under its real id', () { - final rows = cloudLineupRows(_shared()); - - for (final order in _orders(rows)) { - final read = lineUpGraphFromCloudRows(order); - expect(read.aliases.isEmpty, isTrue); - expect(read.graph.origins.map((o) => o.id).toSet(), {'a', 'b'}); - expect(read.graph.landings.map((l) => l.id).toSet(), {'shared', 'other'}); - expect(_ends(read), { - 'k1': ('a', 'shared'), - 'k2': ('b', 'shared'), - 'k3': ('a', 'other'), - }); - } - - // Fan-in and fan-out round-trip: the page drawn is the page written, - // and writing it again gives back the same rows. - final read = lineUpGraphFromCloudRows(rows); - expect(_json(read.graph), _json(_shared())); - final written = cloudLineupRows(read.graph, aliases: read.aliases); - expect(_canonical(written), _canonical(rows)); - }); - - test('copies that disagree draw apart, the smallest lineup id keeping the id', - () { - for (final order in _orders(_splitLanding())) { - final read = lineUpGraphFromCloudRows(order); - - expect(_ends(read), { - 'k1': ('o1', 'shared'), - 'k2': ('o2', 'shared@k2'), - 'k3': ('o3', 'shared@k2'), - }); - expect(_landingAt(read, 'shared'), _here); - expect(_landingAt(read, 'shared@k2'), _moved); - expect(read.graph.landings, hasLength(2)); - expect(read.aliases.landings, {'shared@k2': 'shared'}); - expect(read.aliases.origins, isEmpty); - } - - // Which copy keeps the id follows the lineup ids, not which copy is - // newer: here the moved copy is k1's. - final read = lineUpGraphFromCloudRows([ - _row('k2', _origin('o2'), _landing('shared')), - _row('k1', _origin('o1'), _landing('shared', position: _moved)), - ]); - expect(_ends(read), { - 'k2': ('o2', 'shared@k2'), - 'k1': ('o1', 'shared'), + test('lineups connected through a spot share one row with their spots', () { + final written = cloudLineupRows(_page()); + + expect(written.rows.map((row) => row.publicId), ['k1', 'k9']); + final group = written.rows.first; + expect(group.payload['kind'], cloudLineupsPayloadKind); + expect(group.payload['payloadVersion'], currentCloudPayloadVersion); + final data = _data(group); + expect(data['id'], 'k1'); + expect(_ids(data['origins']), ['a', 'b']); + expect(_ids(data['landings']), ['shared', 'other']); + expect(_ids(data['links']), ['k1', 'k2', 'k3']); + expect( + (data['links'] as List).first, + jsonDecode(jsonEncode(_page().links.first.toJson())), + reason: 'a lineup is stored as the canvas has it', + ); + expect(_ids(_data(written.rows.last)['links']), ['k9']); + expect(written.groupOf, { + for (final id in ['a', 'b', 'shared', 'other', 'k1', 'k2', 'k3']) + id: 'k1', + for (final id in ['z', 'far', 'k9']) id: 'k9', }); - expect(_landingAt(read, 'shared'), _moved); - expect(_landingAt(read, 'shared@k2'), _here); - }); - - test('spots appear in the order the rows first carry them', () { - final read = lineUpGraphFromCloudRows(_splitLanding().reversed); - - expect(read.graph.origins.map((o) => o.id), ['o3', 'o2', 'o1']); - expect(read.graph.landings.map((l) => l.id), ['shared@k2', 'shared']); - expect(read.graph.links.map((l) => l.id), ['k3', 'k2', 'k1']); }); - test('three copies that disagree draw three spots', () { - final rows = [ - _row('k1', _origin('o1'), _landing('shared')), - _row('k2', _origin('o2'), _landing('shared', position: _moved)), - _row('k3', _origin('o3'), _landing('shared', position: _elsewhere)), - _row('k4', _origin('o4'), _landing('shared', position: _moved)), - ]; - - for (final order in _orders(rows)) { - final read = lineUpGraphFromCloudRows(order); + test('rows draw back the page exactly, and write back the same rows', () { + final written = cloudLineupRows(_page()); + final read = lineUpGraphFromCloudRows(written.rows); - expect(_ends(read), { - 'k1': ('o1', 'shared'), - 'k2': ('o2', 'shared@k2'), - 'k3': ('o3', 'shared@k3'), - 'k4': ('o4', 'shared@k2'), - }); - expect(_landingAt(read, 'shared'), _here); - expect(_landingAt(read, 'shared@k2'), _moved); - expect(_landingAt(read, 'shared@k3'), _elsewhere); - expect(read.aliases.landings, { - 'shared@k2': 'shared', - 'shared@k3': 'shared', - }); - } - }); - - test('an origin and a landing split on their own', () { - const far = Offset(70, 80); - final read = lineUpGraphFromCloudRows([ - _row('k1', _origin('a'), _landing('l')), - // Only the origin disagrees. - _row('k2', _origin('a', position: far), _landing('l')), - // Only the landing disagrees. - _row('k3', _origin('a'), _landing('l', position: _moved)), - ]); - - expect(_ends(read), { - 'k1': ('a', 'l'), - 'k2': ('a@k2', 'l'), - 'k3': ('a', 'l@k3'), - }); - expect(_originAt(read, 'a'), const Offset(10, 20)); - expect(_originAt(read, 'a@k2'), far); - expect(_landingAt(read, 'l'), _here); - expect(_landingAt(read, 'l@k3'), _moved); - expect(read.aliases.origins, {'a@k2': 'a'}); - expect(read.aliases.landings, {'l@k3': 'l'}); + expect(read.groupOf, written.groupOf); + final rewritten = cloudLineupRows(read.graph, groupOf: read.groupOf); + expect(_canonical(rewritten.rows), _canonical(written.rows)); + // Graph order is rows' order: the round trip keeps every lineup, spot + // and field. + expect( + _json(read.graph), + _json(LineUpGraph( + origins: [_origin('a'), _origin('b'), _origin('z')], + landings: [_landing('shared'), _landing('other'), _landing('far')], + links: [_page().links[0], _page().links[1], _page().links[3]] + + [_page().links[2]], + )), + ); }); - test('an unchanged page writes back every row exactly', () { - final pages = { - 'agreeing': cloudLineupRows(_shared()), - 'split landing': _splitLanding(), - 'split origin and landing': [ - _row('k1', _origin('a'), _landing('l')), - _row('k2', _origin('a', position: _elsewhere), _landing('l')), - _row('k3', _origin('a'), _landing('l', position: _moved)), - _row('k4', _origin('a', position: _elsewhere), _landing('l')), + test('a group keeps its id when the lineup it was named after goes', () { + final read = lineUpGraphFromCloudRows(cloudLineupRows(_page()).rows); + final withoutK1 = LineUpGraph( + origins: read.graph.origins, + landings: read.graph.landings, + links: [ + for (final link in read.graph.links) + if (link.id != 'k1') link, ], - }; - - for (final MapEntry(key: page, value: rows) in pages.entries) { - // Rows as the server returns them. - final stored = [ - for (final row in rows) - CloudLineupRow( - publicId: row.publicId, - payload: _plain(row.payload) as Map, - ), - ]; - final hydrated = lineUpGraphFromCloudRows(stored); - - final written = cloudLineupRows( - hydrated.graph, - aliases: hydrated.aliases, - ); + ); - expect(_canonical(written), _canonical(stored), reason: page); - } + final rows = cloudLineupRows(withoutK1, groupOf: read.groupOf).rows; + expect(rows.map((row) => row.publicId), ['k1', 'k9']); + expect(_ids(_data(rows.first)['links']), ['k2', 'k3']); }); - test('moving a spot drawn apart changes only the rows carrying it', () { - final rows = _splitLanding(); - final hydrated = lineUpGraphFromCloudRows(rows); - LineUpGraph moving(String id, Offset to) => LineUpGraph( - origins: hydrated.graph.origins, - landings: [ - for (final landing in hydrated.graph.landings) - landing.id == id - ? LineUpLanding( - id: id, - ability: landing.ability.copyWith(position: to), - ) - : landing, - ], - links: hydrated.graph.links, - ); - - // The aliased spot: k2 and k3 change, under the real id. - final aliasMoved = cloudLineupRows( - moving('shared@k2', _elsewhere), - aliases: hydrated.aliases, + test('a group never splits: halves stay in its row', () { + // k1 (a -> shared) and k3 (b -> l2) connect only through k2 + // (b -> shared). + final graph = LineUpGraph( + origins: [_origin('a'), _origin('b')], + landings: [_landing('shared'), _landing('l2')], + links: [ + _link('k1', 'a', 'shared'), + _link('k2', 'b', 'shared'), + _link('k3', 'b', 'l2'), + ], ); - expect(_canonical([aliasMoved[0]]), _canonical([rows[0]])); - for (final row in aliasMoved.skip(1)) { - final landing = _end(row, 'landing'); - expect(landing['id'], 'shared'); - expect((landing['ability'] as Map)['lineUpID'], 'shared'); - expect( - _plain(landing), - _plain(_landing('shared', position: _elsewhere).toJson()), - ); - } - expect(_end(aliasMoved[1], 'landing'), _end(aliasMoved[2], 'landing')); + final read = lineUpGraphFromCloudRows(cloudLineupRows(graph).rows); - // The spot that kept the id: only k1 changes. - final realMoved = cloudLineupRows( - moving('shared', _elsewhere), - aliases: hydrated.aliases, + // Deleting k2 leaves {k1} and {k3} unconnected. + final split = LineUpGraph( + origins: graph.origins, + landings: graph.landings, + links: [graph.links[0], graph.links[2]], ); + final rows = cloudLineupRows(split, groupOf: read.groupOf).rows; + expect(rows.map((row) => row.publicId), ['k1']); + expect(_ids(_data(rows.single)['links']), ['k1', 'k3']); + + // Without what the rows said, the halves would be two new groups. expect( - _plain(_end(realMoved[0], 'landing')), - _plain(_landing('shared', position: _elsewhere).toJson()), + cloudLineupRows(split).rows.map((row) => row.publicId), + ['k1', 'k3'], ); - expect(_canonical(realMoved.skip(1)), _canonical(rows.skip(1))); }); - test("a marker's lineUpID follows its spot's alias, and back", () { - final rows = [ - _row('k1', _origin('a'), _landing('l')), - _row('k2', _origin('a', position: _moved), - _landing('l', position: _moved)), - // A marker that never named its spot keeps what it named. - _row( - 'k3', - _origin('a', position: _elsewhere, lineUpID: 'unrelated'), - _landing('l'), - ), - ]; - final hydrated = lineUpGraphFromCloudRows(rows); - - final origins = { - for (final origin in hydrated.graph.origins) - origin.id: origin.agent.lineUpID, - }; - expect(origins, {'a': 'a', 'a@k2': 'a@k2', 'a@k3': 'unrelated'}); - expect( - hydrated.graph.landings - .firstWhere((l) => l.id == 'l@k2') - .ability - .lineUpID, - 'l@k2', + test('a new lineup on a spot joins that spot\'s group', () { + final read = lineUpGraphFromCloudRows(cloudLineupRows(_page()).rows); + final added = LineUpGraph( + origins: [...read.graph.origins, _origin('c')], + landings: read.graph.landings, + links: [...read.graph.links, _link('k0', 'c', 'far')], ); - final written = cloudLineupRows(hydrated.graph, aliases: hydrated.aliases); - expect((_end(written[1], 'origin')['agent'] as Map)['lineUpID'], 'a'); - expect((_end(written[1], 'landing')['ability'] as Map)['lineUpID'], 'l'); - expect( - (_end(written[2], 'origin')['agent'] as Map)['lineUpID'], - 'unrelated', - ); - expect(_canonical(written), _canonical(rows)); + final written = cloudLineupRows(added, groupOf: read.groupOf); + expect(written.rows.map((row) => row.publicId), ['k1', 'k9']); + expect(_ids(_data(written.rows.last)['links']), ['k9', 'k0']); + expect(written.groupOf['k0'], 'k9'); + expect(written.groupOf['c'], 'k9'); }); - test('a lineup added to a spot drawn apart is written under the real id', () { - final rows = _splitLanding(); - final hydrated = lineUpGraphFromCloudRows(rows); - final added = LineUpGraph( - origins: [...hydrated.graph.origins, _origin('o4')], - landings: hydrated.graph.landings, - links: [ - ...hydrated.graph.links, - LineUpLink(id: 'k4', originId: 'o4', landingId: 'shared@k2'), - ], + test('a lineup joining two groups merges them under the smaller id', () { + final read = lineUpGraphFromCloudRows(cloudLineupRows(_page()).rows); + final bridged = LineUpGraph( + origins: read.graph.origins, + landings: read.graph.landings, + links: [...read.graph.links, _link('k5', 'z', 'shared')], ); - final written = cloudLineupRows(added, aliases: hydrated.aliases); - - expect(_canonical(written.take(3)), _canonical(rows)); - expect( - _plain(_end(written[3], 'landing')), _plain(_end(rows[1], 'landing'))); - expect(_end(written[3], 'landing')['id'], 'shared'); - // And it is drawn again on the spot it was added to. - expect(_ends(lineUpGraphFromCloudRows(written))['k4'], ('o4', 'shared@k2')); + final rows = cloudLineupRows(bridged, groupOf: read.groupOf).rows; + expect(rows.map((row) => row.publicId), ['k1']); + expect(_ids(_data(rows.single)['links']), ['k1', 'k2', 'k3', 'k9', 'k5']); }); - test('aliases followed by more aliases keep both', () { - const first = CloudLineupAliases( - origins: {'a@k2': 'a'}, - landings: {'l@k3': 'l'}, + test('a new group never takes a group id already in use', () { + final graph = LineUpGraph( + origins: [_origin('a')], + landings: [_landing('l')], + links: [_link('k1', 'a', 'l'), _link('k2', 'a', 'l')], ); - const second = CloudLineupAliases(origins: {'b@k5': 'b'}); - - final both = first.followedBy(second); - expect(both.origins, {'a@k2': 'a', 'b@k5': 'b'}); - expect(both.landings, {'l@k3': 'l'}); - expect(identical(first.followedBy(CloudLineupAliases.none), first), isTrue); - expect(CloudLineupAliases.none.isEmpty, isTrue); + expect( + cloudLineupRows(graph, takenGroupIds: {'k1'}).rows.single.publicId, + 'k2', + ); + // A group id remembered for a lineup elsewhere is in use too. + expect( + cloudLineupRows(graph, groupOf: {'gone': 'k1'}).rows.single.publicId, + 'k2', + ); + // Every lineup id taken: a fresh id. + final fresh = cloudLineupRows(graph, takenGroupIds: {'k1', 'k2'}); + expect(fresh.rows.single.publicId, isNot(anyOf('k1', 'k2'))); + expect(fresh.groupOf['k1'], fresh.rows.single.publicId); }); - test('a link missing its origin or landing has no row', () { + test('a lineup missing a spot, and a spot no lineup uses, are not written', + () { final rows = cloudLineupRows(LineUpGraph( - origins: [_origin('a')], + origins: [_origin('a'), _origin('lonely')], landings: [_landing('l')], links: [ - LineUpLink(id: 'k1', originId: 'a', landingId: 'l'), - LineUpLink(id: 'dangling', originId: 'a', landingId: 'missing'), + _link('k1', 'a', 'l'), + _link('dangling', 'a', 'missing'), ], - )); + )).rows; expect(rows.map((row) => row.publicId), ['k1']); + expect(_ids(_data(rows.single)['origins']), ['a']); + expect(_ids(_data(rows.single)['links']), ['k1']); + }); + + test('the first row naming a spot or lineup draws it', () { + // Two clients put lineups on spot `shared` into different groups. + final first = cloudLineupRows(LineUpGraph( + origins: [_origin('a')], + landings: [_landing('shared')], + links: [_link('k1', 'a', 'shared')], + )).rows.single; + final second = cloudLineupRows(LineUpGraph( + origins: [_origin('b')], + landings: [_landing('shared', position: const Offset(99, 99))], + links: [_link('k2', 'b', 'shared')], + )).rows.single; + + final read = lineUpGraphFromCloudRows([first, second]); + expect(read.graph.landings.map((l) => l.id), ['shared']); + expect(read.graph.landings.single.ability.position, const Offset(30, 40)); + expect(read.graph.links.map((l) => l.landingId), ['shared', 'shared']); + expect(read.groupOf['shared'], 'k1'); + + // Written back, they become one group: the spot connects them. + final rows = cloudLineupRows(read.graph, groupOf: read.groupOf).rows; + expect(rows.map((row) => row.publicId), ['k1']); }); test('a row of any other shape fails loudly, naming the row', () { - final valid = cloudLineupRows(_shared()).first.payload; - final data = valid['data'] as Map; + final valid = cloudLineupRows(_page()).rows.first.payload; + final data = Map.from(valid['data'] as Map); final broken = { - // The graph rows of protocol 4 and the legacy group are not lineups. + // Protocol 4's graph rows, the legacy group, and the per-lineup row + // this replaced are not groups. 'lineupLink': {...valid, 'kind': 'lineupLink'}, 'lineupGroup': {...valid, 'kind': 'lineupGroup'}, - 'no origin': { + 'lineup': {...valid, 'kind': 'lineup'}, + 'no origins': { ...valid, - 'data': {...data}..remove('origin'), + 'data': {...data}..remove('origins'), }, - 'no landing': { + 'no links': { ...valid, - 'data': {...data}..remove('landing'), + 'data': {...data}..remove('links'), + }, + 'a lineup aimed outside the row': { + ...valid, + 'data': { + ...data, + 'landings': [ + for (final landing in data['landings'] as List) + if ((landing as Map)['id'] != 'other') landing, + ], + }, + }, + 'not objects': { + ...valid, + 'data': { + ...data, + 'origins': ['a'], + }, }, }; for (final MapEntry(key: reason, value: payload) in broken.entries) { @@ -441,7 +295,7 @@ void main() { throwsA(isA().having( (error) => error.message, 'message', - contains('Cloud lineup k1 could not be read'), + contains('Cloud lineup group k1 could not be read'), )), reason: reason, ); @@ -453,14 +307,14 @@ void main() { throwsA(isA().having( (error) => error.message, 'message', - contains('Cloud lineup someone-else could not be read'), + contains('Cloud lineup group someone-else could not be read'), )), - reason: 'a row keyed apart from its lineup', + reason: 'a row keyed apart from its group', ); }); - test('ops in the cloud format before one row per lineup are retired', () { - final payload = cloudLineupRows(_shared()).first.payload; + test('ops in a cloud format before lineup group rows are retired', () { + final payload = cloudLineupRows(_page()).rows.first.payload; expect( isRetiredCloudLineupOp(LineupAddOp( opId: 'op', @@ -471,16 +325,19 @@ void main() { )), isFalse, ); - expect( - isRetiredCloudLineupOp(LineupAddOp( - opId: 'op', - lineupPublicId: 'lineupLink:k1', - pagePublicId: 'p', - payload: {...payload, 'kind': 'lineupLink'}, - sortIndex: 0, - )), - isTrue, - ); + for (final kind in ['lineupLink', 'lineup']) { + expect( + isRetiredCloudLineupOp(LineupAddOp( + opId: 'op', + lineupPublicId: 'k1', + pagePublicId: 'p', + payload: {...payload, 'kind': kind}, + sortIndex: 0, + )), + isTrue, + reason: kind, + ); + } for (final key in ['lineupOrigin:a', 'lineupLanding:l', 'lineupLink:k']) { expect( isRetiredCloudLineupOp(LineupDeleteOp( diff --git a/test/cloud_payload_upgrade_test.dart b/test/cloud_payload_upgrade_test.dart index be9808b5..5ec670b1 100644 --- a/test/cloud_payload_upgrade_test.dart +++ b/test/cloud_payload_upgrade_test.dart @@ -112,7 +112,7 @@ void main() { links: [ LineUpLink(id: 'lineup', originId: 'origin', landingId: 'landing'), ], - )).single.payload; + )).rows.single.payload; expect(lineup['payloadVersion'], currentCloudPayloadVersion); expect(identical(upgradeCloudPayload(lineup, _map), lineup), isTrue); diff --git a/test/collab_sync_models_test.dart b/test/collab_sync_models_test.dart index 62b46964..65e975c3 100644 --- a/test/collab_sync_models_test.dart +++ b/test/collab_sync_models_test.dart @@ -395,34 +395,45 @@ void main() { test('cloud payload data normalizes nested bridge maps for lineup parsing', () { final payload = { - 'kind': cloudLineupPayloadKind, + 'kind': cloudLineupsPayloadKind, 'payloadVersion': 1, 'data': { - 'id': 'lineup-1', - 'name': 'Garage smoke', - 'origin': { - 'id': 'origin-1', - 'agent': { - 'id': 'agent-1', - 'type': 'sova', - 'position': {'dx': 10, 'dy': 20}, - 'lineUpID': 'origin-1', + 'id': 'group-1', + 'origins': [ + { + 'id': 'origin-1', + 'agent': { + 'id': 'agent-1', + 'type': 'sova', + 'position': {'dx': 10, 'dy': 20}, + 'lineUpID': 'origin-1', + }, }, - }, - 'landing': { - 'id': 'landing-1', - 'ability': { - 'id': 'ability-1', - 'data': {'type': 'sova', 'index': 2.0}, - 'position': {'dx': 30, 'dy': 40}, - 'lineUpID': 'landing-1', + ], + 'landings': [ + { + 'id': 'landing-1', + 'ability': { + 'id': 'ability-1', + 'data': {'type': 'sova', 'index': 2.0}, + 'position': {'dx': 30, 'dy': 40}, + 'lineUpID': 'landing-1', + }, }, - }, + ], + 'links': [ + { + 'id': 'lineup-1', + 'originId': 'origin-1', + 'landingId': 'landing-1', + 'name': 'Garage smoke', + }, + ], }, }; final read = lineUpGraphFromCloudRows([ - CloudLineupRow(publicId: 'lineup-1', payload: payload), + CloudLineupRow(publicId: 'group-1', payload: payload), ]); final graph = read.graph; final landing = graph.landings.single; @@ -432,7 +443,7 @@ void main() { expect(landing.id, 'landing-1'); expect(landing.ability.lineUpID, 'landing-1'); expect(landing.ability.position, const Offset(30, 40)); - expect(read.aliases.isEmpty, isTrue); + expect(read.groupOf['lineup-1'], 'group-1'); }); test('ability info accepts Convex float64 integers and rejects fractions', @@ -511,7 +522,7 @@ void main() { isA().having( (error) => error.message, 'message', - contains('Cloud lineup lineup-1 could not be read'), + contains('Cloud lineup group lineup-1 could not be read'), ), ), ); diff --git a/test/global_strategy_outbox_test.dart b/test/global_strategy_outbox_test.dart index c0558107..c7ce9831 100644 --- a/test/global_strategy_outbox_test.dart +++ b/test/global_strategy_outbox_test.dart @@ -249,10 +249,17 @@ void main() { opId: 'add-lineup', lineupPublicId: 'k', pagePublicId: 'page-one', - payload: cloudLineupPayload({ + payload: cloudLineupsPayload({ 'id': 'k', - 'origin': {'id': 'o', 'agent': {}}, - 'landing': {'id': 'l', 'ability': {}}, + 'origins': [ + {'id': 'o', 'agent': {}}, + ], + 'landings': [ + {'id': 'l', 'ability': {}}, + ], + 'links': [ + {'id': 'k', 'originId': 'o', 'landingId': 'l'}, + ], }), sortIndex: 0, ); diff --git a/test/lineup_group_changes_test.dart b/test/lineup_group_changes_test.dart new file mode 100644 index 00000000..3c9bbd24 --- /dev/null +++ b/test/lineup_group_changes_test.dart @@ -0,0 +1,139 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:icarus/const/agents.dart'; +import 'package:icarus/const/line_provider.dart'; +import 'package:icarus/const/placed_classes.dart'; +import 'package:icarus/strategy/lineup_group_changes.dart'; + +LineUpOrigin _origin(String id, {Offset position = const Offset(10, 20)}) => + LineUpOrigin( + id: id, + agent: PlacedAgent( + id: 'agent-$id', + type: AgentType.brimstone, + position: position, + lineUpID: id, + ), + ); + +LineUpLanding _landing(String id, {Offset position = const Offset(30, 40)}) => + LineUpLanding( + id: id, + ability: PlacedAbility( + id: 'ability-$id', + data: AgentData.agents[AgentType.brimstone]!.abilities[2], + position: position, + lineUpID: id, + ), + ); + +/// Three smokes from o1, o2, o3 onto one landing; k3 has no name. +LineUpGraph _group({ + Offset landingAt = const Offset(30, 40), + String k1Notes = '', + List extra = const [], + bool withoutK2 = false, +}) => + LineUpGraph( + origins: [_origin('o1'), _origin('o2'), _origin('o3')], + landings: [_landing('l1', position: landingAt)], + links: [ + LineUpLink( + id: 'k1', + originId: 'o1', + landingId: 'l1', + name: 'B Main 1', + notes: k1Notes, + ), + if (!withoutK2) + LineUpLink( + id: 'k2', originId: 'o2', landingId: 'l1', name: 'B Main 2'), + LineUpLink(id: 'k3', originId: 'o3', landingId: 'l1'), + ...extra, + ], + ); + +Map _described(List changes) => { + for (final change in changes) change.label: change.description, + }; + +void main() { + test('nothing changed lists nothing', () { + expect(lineupChanges(from: _group(), to: _group()), isEmpty); + }); + + test('each changed lineup is listed once, with everything that changed', () { + final changes = lineupChanges( + from: _group(), + to: _group(landingAt: const Offset(99, 99), k1Notes: 'jump throw'), + ); + + // Moving the shared landing moves every lineup on it. + expect(_described(changes), { + 'B Main 1': 'notes edited, landing moved', + 'B Main 2': 'landing moved', + 'Brimstone lineup': 'landing moved', + }); + }); + + test('added and deleted lineups are named as such', () { + final changes = lineupChanges( + from: _group(), + to: _group( + withoutK2: true, + extra: [ + LineUpLink(id: 'k4', originId: 'o1', landingId: 'l1', name: 'New'), + ], + ), + ); + + expect(_described(changes), {'New': 'added', 'B Main 2': 'deleted'}); + expect(changes.last.label, 'B Main 2', reason: 'deletions come last'); + }); + + test('a deleted group lists every lineup as deleted', () { + final changes = lineupChanges(from: _group(), to: LineUpGraph.empty); + + expect(changes.map((change) => change.description).toSet(), {'deleted'}); + expect(changes, hasLength(3)); + }); + + test('a fork has fresh ids everywhere and the same lineups', () { + final original = _group(k1Notes: 'jump throw'); + final fork = forkLineUpGraph(original); + + final originalIds = { + for (final origin in original.origins) ...[origin.id, origin.agent.id], + for (final landing in original.landings) ...[ + landing.id, + landing.ability.id, + ], + for (final link in original.links) link.id, + }; + final forkIds = { + for (final origin in fork.origins) ...[origin.id, origin.agent.id], + for (final landing in fork.landings) ...[landing.id, landing.ability.id], + for (final link in fork.links) link.id, + }; + expect(forkIds.intersection(originalIds), isEmpty); + expect(forkIds, hasLength(originalIds.length)); + + // The fork is wired like the original: three lineups on one landing, + // each marker naming its own spot. + expect(fork.links.map((link) => link.landingId).toSet(), { + fork.landings.single.id, + }); + for (final origin in fork.origins) { + expect(origin.agent.lineUpID, origin.id); + } + expect(fork.landings.single.ability.lineUpID, fork.landings.single.id); + + // And otherwise the same: nothing a user sees differs. + String described(LineUpGraph graph) => [ + for (final link in graph.links) + '${link.name}|${link.notes}|' + '${graph.origins.firstWhere((o) => o.id == link.originId).agent.position}|' + '${graph.landings.firstWhere((l) => l.id == link.landingId).ability.position}', + ].join(';'); + expect(described(fork), described(original)); + }); +} diff --git a/test/remote_page_merge_test.dart b/test/remote_page_merge_test.dart index c0c20acf..df2c7a58 100644 --- a/test/remote_page_merge_test.dart +++ b/test/remote_page_merge_test.dart @@ -85,6 +85,21 @@ void main() { _link('k3', 'o3', 'l3', 'k3 renamed'), ], ); + // The group rows: g1 holds k1 and k2 with their spots, g3 holds k3 (and + // a teammate's k4, when there is one). + const groups = { + 'k1': 'g1', + 'o1': 'g1', + 'k2': 'g1', + 'o2': 'g1', + 'shared': 'g1', + 'k3': 'g3', + 'o3': 'g3', + 'l3': 'g3', + 'k4': 'g3', + 'o4': 'g3', + }; + String? groupOf(String id) => groups[id]; Map names(LineUpGraph graph) => {for (final link in graph.links) link.id: link.name}; Map spots(LineUpGraph graph) => { @@ -95,19 +110,27 @@ void main() { }; test('nothing held takes the server copy whole', () { - final (graph, held) = - mergeHeldLineups(local: local, remote: remote, holding: {'x'}); + final (graph, held) = mergeHeldLineups( + local: local, + remote: remote, + holding: {'x'}, + groupOf: groupOf, + ); expect(held, isEmpty); expect(identical(graph, remote), isTrue); }); - test('holding a spot holds every lineup sharing it, and only those', () { - final (graph, held) = - mergeHeldLineups(local: local, remote: remote, holding: {'o1'}); + test('holding a spot holds its whole group, and only that', () { + final (graph, held) = mergeHeldLineups( + local: local, + remote: remote, + holding: {'o1'}, + groupOf: groupOf, + ); - // o1 holds k1; k1 shares its landing with k2, so k2 waits too. - expect(held, {'k1', 'k2'}); + // o1 holds group g1, so k2 waits with k1. + expect(held, {'g1'}); expect(names(graph), {'k1': 'k1', 'k2': 'k2', 'k3': 'k3 renamed'}); expect(spots(graph), { 'o1': 1, @@ -127,10 +150,14 @@ void main() { links: [...remote.links, _link('k4', 'o4', 'l3')], ); - final (graph, held) = - mergeHeldLineups(local: local, remote: withNew, holding: {'l3'}); + final (graph, held) = mergeHeldLineups( + local: local, + remote: withNew, + holding: {'l3'}, + groupOf: groupOf, + ); - expect(held, {'k3', 'k4'}); + expect(held, {'g3'}); expect(graph.links.map((link) => link.id), ['k1', 'k2', 'k3']); expect(graph.origins.map((origin) => origin.id), ['o1', 'o2', 'o3']); expect(spots(graph)['l3'], 1); @@ -143,12 +170,44 @@ void main() { links: [remote.links[1], remote.links[2]], ); - final (graph, held) = - mergeHeldLineups(local: local, remote: without, holding: {'k1'}); + final (graph, held) = mergeHeldLineups( + local: local, + remote: without, + holding: {'k1'}, + groupOf: groupOf, + ); - expect(held, {'k1', 'k2'}); + expect(held, {'g1'}); expect(graph.links.map((link) => link.id), ['k1', 'k2', 'k3']); expect(spots(graph)['o1'], 1); }); + + test('a group holds lineups no spot connects any more', () { + // k3 is in g1 too: the lineup that joined it to k1 was deleted, and + // a group never splits. + String? oneGroup(String id) => id == 'x' ? null : 'g1'; + + final (graph, held) = mergeHeldLineups( + local: local, + remote: remote, + holding: {'k1'}, + groupOf: oneGroup, + ); + + expect(held, {'g1'}); + expect(names(graph), {'k1': 'k1', 'k2': 'k2', 'k3': 'k3'}); + }); + + test('a lineup in no group yet holds what shares its spots', () { + final (graph, held) = mergeHeldLineups( + local: local, + remote: remote, + holding: {'o1'}, + groupOf: (_) => null, + ); + + expect(held, isEmpty); + expect(names(graph), {'k1': 'k1', 'k2': 'k2', 'k3': 'k3 renamed'}); + }); }); } diff --git a/test/strategy_cloud_migration_test.dart b/test/strategy_cloud_migration_test.dart index 7fe2172b..ec5ca212 100644 --- a/test/strategy_cloud_migration_test.dart +++ b/test/strategy_cloud_migration_test.dart @@ -78,7 +78,7 @@ List _upload( /// server returns them. LineUpGraph _hydrate(List adds) => _hydrated(adds).graph; -/// [_hydrate], with the aliases drawing it took. +/// [_hydrate], with the group each lineup and spot was drawn from. CloudLineups _hydrated(List adds) { return lineUpGraphFromCloudRows([ for (final add in adds) @@ -95,15 +95,16 @@ String _canonical(Object? value) => void main() { TestWidgetsFlutterBinding.ensureInitialized(); - test('a lineup is uploaded as one row carrying its origin and landing', () { + test('a lineup is uploaded as a group row holding it and its spots', () { final adds = _upload(_sharedIdLineup()); expect(adds.map((add) => add.lineupPublicId), ['link-1']); - expect(adds.single.payload['kind'], cloudLineupPayloadKind); + expect(adds.single.payload['kind'], cloudLineupsPayloadKind); expect(adds.single.sortIndex, 0); final data = cloudPayloadData(adds.single.payload); - expect((data['origin'] as Map)['id'], 'origin-1'); - expect((data['landing'] as Map)['id'], 'link-1'); + expect((data['origins'] as List).single['id'], 'origin-1'); + expect((data['landings'] as List).single['id'], 'link-1'); + expect((data['links'] as List).single['id'], 'link-1'); // Hydrating the upload gives back the page's graph, ids and all, so the // page never authors a patch for a lineup nobody touched. expect( @@ -112,29 +113,28 @@ void main() { ); }); - test('a lineup id another page already took is reassigned', () { + test('a group id another page already took is not reused', () { + final usedLineupIds = {'link-1'}; final adds = _upload( _sharedIdLineup(), - // A page duplicated from page 1 already uploaded this lineup. - usedLineupIds: {'link-1'}, + // A page duplicated from page 1 already uploaded this group. + usedLineupIds: usedLineupIds, ); final newId = adds.single.lineupPublicId; expect(newId, isNot('link-1')); + expect(usedLineupIds, {'link-1', newId}); final data = cloudPayloadData(adds.single.payload); expect(data['id'], newId); - // Spots are shared within a page only, so its ends keep their ids. - expect((data['origin'] as Map)['id'], 'origin-1'); - expect((data['landing'] as Map)['id'], 'link-1'); + // Ids inside a row only need to be unique in the row, so they stay. + expect((data['links'] as List).single['id'], 'link-1'); + expect((data['origins'] as List).single['id'], 'origin-1'); // And the rows reproduce exactly when the hydrated graph is sent again. final hydrated = _hydrated(adds); - final resent = cloudLineupRows( - hydrated.graph, - aliases: hydrated.aliases, - ); + final resent = cloudLineupRows(hydrated.graph, groupOf: hydrated.groupOf); expect( - _canonical([for (final row in resent) row.payload]), + _canonical([for (final row in resent.rows) row.payload]), _canonical([for (final add in adds) add.payload]), ); }); @@ -171,11 +171,8 @@ void main() { final adds = _upload(fanIn); - expect(adds.map((add) => add.lineupPublicId), [ - 'link-a', - 'link-b', - 'link-c', - ]); + // Connected through their spots, the three lineups are one group. + expect(adds.map((add) => add.lineupPublicId), ['link-a']); final hydrated = _hydrate(adds); expect(hydrated.landings.map((landing) => landing.id), [ 'shared', diff --git a/test/strategy_op_queue_provider_test.dart b/test/strategy_op_queue_provider_test.dart index e2ab301b..4916c7ab 100644 --- a/test/strategy_op_queue_provider_test.dart +++ b/test/strategy_op_queue_provider_test.dart @@ -3220,6 +3220,69 @@ void main() { expect(store.load().records, isEmpty); }); + test( + 'work deleted on both sides stays in attention when its refusal was ' + 'not a deletion', () async { + final store = MemoryDurableStrategyOutboxStore(); + final repository = _TombstoneRepository(); + // Refused as deleted: a teammate deleted element-1. + const elementPatch = ElementPatchOp( + opId: 'move-element', + elementPublicId: 'element-1', + pagePublicId: 'page-1', + payload: {'value': 'moved'}, + sortIndex: 4, + expectedElementRevision: 1, + ); + // Refused as a revision mismatch: an add of lineup group k against a + // revision it no longer has. That says nothing about whether the + // server deleted k, never had it, or holds it elsewhere. + final staleAdd = LineupAddOp( + opId: 'stale-add', + lineupPublicId: 'k', + pagePublicId: 'page-1', + payload: _lineupAdd(opId: 'unused').payload, + sortIndex: 0, + expectedLineupRevision: 1, + ); + final elementKey = EntitySyncKey.forStrategyOp(elementPatch)!; + final lineupKey = EntitySyncKey.forStrategyOp(staleAdd)!; + final container = _cloudQueueContainer( + store: store, + repository: repository, + ); + addTearDown(container.dispose); + final notifier = container.read(strategyOpQueueProvider.notifier) + ..setActiveStrategy('strategy-1', accountId: 'account-a'); + await notifier.enqueue(elementPatch, flushImmediately: false); + await notifier.enqueue(staleAdd, flushImmediately: false); + await notifier.flushNow(); + await _settle(); + expect( + container.read(strategyOpQueueProvider).attentionByEntityKey.keys, + unorderedEquals([elementKey, lineupKey]), + ); + expect(notifier.refusedAsDeleted(elementKey), isTrue); + expect(notifier.refusedAsDeleted(lineupKey), isFalse); + + // The user deleted both on the canvas. + final settled = await notifier.settleAttention({elementKey, lineupKey}); + + // Only the work refused as deleted is dropped; the other still waits, + // in memory and on disk, for the user to choose. + expect(settled, {elementKey}); + expect( + container.read(strategyOpQueueProvider).attentionByEntityKey.keys, + [lineupKey], + ); + expect(container.read(strategyOpQueueProvider).needsAttention, isTrue); + final records = store.load().records; + expect([for (final record in records) record.entityKey], [lineupKey]); + expect(records.single.status, DurableOutboxStatus.attention); + expect(records.single.lastError, + OpRejectionReason.revisionMismatch.wireName); + }); + test('discarding instead waits for the server copy to be adopted', () async { // The contrast that makes settling distinct: after Use cloud the @@ -3498,11 +3561,17 @@ LineupAddOp _lineupAdd({ opId: opId, lineupPublicId: id, pagePublicId: pageId, - payload: cloudLineupPayload({ + payload: cloudLineupsPayload({ 'id': id, - 'name': name, - 'origin': {'id': 'o', 'agent': {}}, - 'landing': {'id': 'l', 'ability': {}}, + 'origins': [ + {'id': 'o', 'agent': {}}, + ], + 'landings': [ + {'id': 'l', 'ability': {}}, + ], + 'links': [ + {'id': id, 'originId': 'o', 'landingId': 'l', 'name': name}, + ], }), sortIndex: 0, ); diff --git a/test/strategy_page_session_provider_test.dart b/test/strategy_page_session_provider_test.dart index 78dd8e40..abab16a0 100644 --- a/test/strategy_page_session_provider_test.dart +++ b/test/strategy_page_session_provider_test.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:hive_ce/hive.dart'; @@ -40,6 +41,7 @@ import 'package:icarus/providers/collab/active_page_live_sync_models.dart'; import 'package:icarus/providers/collab/active_page_live_sync_provider.dart'; import 'package:icarus/providers/collab/cloud_collab_provider.dart'; import 'package:icarus/providers/collab/cloud_media_upload_queue_provider.dart'; +import 'package:icarus/providers/collab/lineup_conflicts_provider.dart'; import 'package:icarus/providers/collab/remote_strategy_snapshot_provider.dart'; import 'package:icarus/providers/collab/strategy_conflict_provider.dart'; import 'package:icarus/providers/collab/strategy_op_queue_provider.dart'; @@ -55,6 +57,8 @@ import 'package:icarus/providers/text_provider.dart'; import 'package:icarus/providers/transition_provider.dart' hide PageTransitionState; import 'package:icarus/providers/user_preferences_provider.dart'; +import 'package:icarus/strategy/lineup_group_changes.dart'; +import 'package:icarus/strategy/strategy_import_export.dart'; import 'package:icarus/strategy/strategy_page_models.dart'; import 'package:supabase_flutter/supabase_flutter.dart' show User; @@ -102,10 +106,13 @@ class _FakeRemoteEditorNotifier extends RemoteEditorSnapshotNotifier { return page; } + /// While set, a refresh reads nothing, as a read refused or cut off. + bool readFails = false; + @override Future refresh() async { refreshCount += 1; - state = AsyncData(initialSnapshot); + state = readFails ? const AsyncData(null) : AsyncData(initialSnapshot); } @override @@ -489,6 +496,9 @@ class _RecordingMediaQueue extends CloudMediaUploadQueueNotifier { } } +/// Whether this device can reach the server, for tests that go offline. +final _online = StateProvider((ref) => true); + Future _settle() async { await Future.delayed(Duration.zero); await Future.delayed(Duration.zero); @@ -644,29 +654,47 @@ Map _abilityJson( 'armLengthsMeters': [10, 10, 10, 10], }; -/// Origin [id] as a lineup row carries it. +/// Origin [id] as a lineup group row carries it. Map _originJson( String id, [ Offset position = const Offset(10, 20), ]) => {'id': id, 'agent': _agentJson(id, position)}; -/// Landing [id] as a lineup row carries it. +/// Landing [id] as a lineup group row carries it. Map _landingJson( String id, [ Offset position = const Offset(30, 40), ]) => {'id': id, 'ability': _abilityJson(id, position)}; -/// One lineup as the server stores it: one row, the link [id] with its own -/// copies of its [origin] and [landing]. -RemoteLineup _lineupRow( - String pageId, +/// Lineup [id], from origin [originId] to landing [landingId], as a lineup +/// group row carries it. +Map _linkJson( String id, { - required Map origin, - required Map landing, + required String originId, + required String landingId, String name = '', String notes = 'remote lineup', +}) => + { + 'id': id, + 'originId': originId, + 'landingId': landingId, + 'name': name, + 'youtubeLink': '', + 'notes': notes, + 'images': [], + }; + +/// One lineup group as the server stores it: the row [id], holding the +/// group's [origins], [landings] and [links]. +RemoteLineup _groupRow( + String pageId, + String id, { + required List> origins, + required List> landings, + required List> links, int revision = 1, int sortIndex = 0, bool deleted = false, @@ -675,14 +703,11 @@ RemoteLineup _lineupRow( publicId: id, strategyPublicId: 'cloud-strategy', pagePublicId: pageId, - payload: cloudLineupPayload({ + payload: cloudLineupsPayload({ 'id': id, - 'name': name, - 'youtubeLink': '', - 'notes': notes, - 'images': [], - 'origin': origin, - 'landing': landing, + 'origins': origins, + 'landings': landings, + 'links': links, }), sortIndex: sortIndex, revision: revision, @@ -690,8 +715,9 @@ RemoteLineup _lineupRow( ); } -/// A lineup on spots of its own: the link `link-[id]` from the origin [id] -/// to the landing `landing-[id]`. +/// A lineup on spots of its own, alone in its group: the link `link-[id]` +/// from the origin [id] to the landing `landing-[id]`, in the group named +/// after it. RemoteLineup _lineup( String pageId, String id, { @@ -700,23 +726,68 @@ RemoteLineup _lineup( int revision = 1, int sortIndex = 0, }) { - return _lineupRow( + return _groupRow( pageId, 'link-$id', - origin: _originJson(id, agentPosition), - landing: _landingJson('landing-$id'), - name: linkName, + origins: [_originJson(id, agentPosition)], + landings: [_landingJson('landing-$id')], + links: [ + _linkJson('link-$id', + originId: id, landingId: 'landing-$id', name: linkName), + ], revision: revision, sortIndex: sortIndex, ); } -/// The key of the row [_lineup] builds for [id]. +/// The key of the group row [_lineup] builds for [id]. EntitySyncKey _lineupKey(String pageId, String id) => EntitySyncKey.lineup(pageId, 'link-$id'); -/// [row] as a teammate left it: its data changed by [edit], one revision on. -RemoteLineup _editedLineup( +/// The entries of lineup group [data] under [field] (`origins`, +/// `landings` or `links`), to read or to edit in place. +List _entries(Map data, String field) => + [for (final entry in data[field] as List) entry as Map]; + +/// Entry [id] of lineup group [data] under [field]. +Map _entry(Map data, String field, String id) => + _entries(data, field).singleWhere((entry) => entry['id'] == id); + +/// [edit] applied to every lineup in a group's data. +void Function(Map data) _eachLink( + void Function(Map link) edit, +) => + (data) => _entries(data, 'links').forEach(edit); + +/// [edit] applied to every origin's agent in a group's data. +void Function(Map data) _eachAgent( + void Function(Map agent) edit, +) => + (data) => [ + for (final origin in _entries(data, 'origins')) + origin['agent'] as Map, + ].forEach(edit); + +/// [edit] applied to every landing's ability in a group's data. +void Function(Map data) _eachAbility( + void Function(Map ability) edit, +) => + (data) => [ + for (final landing in _entries(data, 'landings')) + landing['ability'] as Map, + ].forEach(edit); + +/// Lineup [id] in the group [payload] carries. +Map _linkIn(CloudPayload? payload, String id) => + _entry(cloudPayloadData(payload!), 'links', id); + +/// The one lineup in the group [payload] carries. +Map _onlyLinkIn(CloudPayload? payload) => + _entries(cloudPayloadData(payload!), 'links').single; + +/// [row] as a teammate left it: its group data changed by [edit], one +/// revision on. +RemoteLineup _editedGroup( RemoteLineup row, void Function(Map data) edit, { bool deleted = false, @@ -734,6 +805,27 @@ RemoteLineup _editedLineup( ); } +/// [row] as a teammate left it: its one lineup changed by [edit], one +/// revision on. +RemoteLineup _editedLineup(RemoteLineup row, void Function(Map link) edit) => + _editedGroup(row, (data) => edit(_entries(data, 'links').single)); + +/// What each of [rows] holds, by group id: its origin, landing and lineup +/// ids, in order. +Map>> _groupsOf( + Iterable rows) => + { + for (final row in rows) + row.publicId: { + for (final field in ['origins', 'landings', 'links']) + field: [ + for (final entry + in _entries(cloudPayloadData(row.payload), field)) + entry['id'], + ], + }, + }; + /// Plays the server's element and lineup rows of one page as convex/ops.ts /// keeps them (applyElementOp, applyLineupOp). /// @@ -744,7 +836,9 @@ RemoteLineup _editedLineup( /// an add expecting the tombstone's revision brings the row back. A write /// that would change nothing is a noop, whatever revision it expected. An op /// sent again (its answer lost) is answered as a noop at the revision it -/// landed at, or refused again. +/// landed at, or refused again. While [recordsRevisions] is off, an op taken +/// is recorded with no revision (as events written before the server kept +/// one), and sent again it is answered as a noop with none. class _FakeServer { _FakeServer( this.pageId, { @@ -766,6 +860,12 @@ class _FakeServer { /// The answer to each element and lineup op taken, by op id. final Map _answers = {}; + /// Whether an op taken is recorded with the revision it landed at. + bool recordsRevisions = true; + + /// Ops taken while [recordsRevisions] was off. + final Set _unrecorded = {}; + /// Every lineup op this server answered, in order. final List received = []; @@ -904,7 +1004,8 @@ class _FakeServer { case final OpAck accepted?: return NoopOpAck( opId: op.opId, - currentRevision: accepted.appliedRevision, + currentRevision: + _unrecorded.contains(op.opId) ? null : accepted.appliedRevision, ); case null: break; @@ -937,11 +1038,12 @@ class _FakeServer { } } + if (!recordsRevisions) _unrecorded.add(op.opId); return _answers[op.opId] = answer(); } - /// A teammate's change to lineup row [id] lands: [edit] changes its data, - /// one revision on. + /// A teammate's change to lineup group row [id] lands: [edit] changes its + /// data, one revision on. void teammateEdit(String id, void Function(Map data) edit) { final row = _lineups[id]!; final payload = jsonDecode(jsonEncode(row.payload)) as CloudPayload; @@ -950,7 +1052,7 @@ class _FakeServer { _lineups[id] = row.next(payload: payload); } - /// A teammate's delete of lineup row [id] lands. + /// A teammate's delete of lineup group row [id] lands. void teammateDelete(String id) => _lineups[id] = _lineups[id]!.next(deleted: true); @@ -958,17 +1060,6 @@ class _FakeServer { void teammateDeleteElement(String id) => _elements[id] = _elements[id]!.next(deleted: true); - /// A teammate's new lineup [row] lands. - void teammateAdd(RemoteLineup row) { - assert(!_lineups.containsKey(row.publicId)); - _lineups[row.publicId] = _Row( - payload: row.payload, - sortIndex: row.sortIndex, - revision: 1, - deleted: false, - ); - } - static Iterable> _sorted(Map rows) => rows.entries.toList() ..sort((a, b) => a.value.sortIndex != b.value.sortIndex @@ -1023,30 +1114,6 @@ class _Row { ); } -/// The origin and landing row [row] carries: their ids, and where their -/// markers stand. -({String origin, Offset originAt, String landing, Offset landingAt}) _endsOf( - RemoteLineup row, -) { - final data = cloudPayloadData(row.payload); - final origin = data['origin'] as Map; - final landing = data['landing'] as Map; - Offset at(Object? marker) { - final position = (marker! as Map)['position'] as Map; - return Offset( - (position['dx'] as num).toDouble(), - (position['dy'] as num).toDouble(), - ); - } - - return ( - origin: origin['id'] as String, - originAt: at(origin['agent']), - landing: landing['id'] as String, - landingAt: at(landing['ability']), - ); -} - RemoteEditorSnapshot _editorSnapshot({ required List pages, required RemotePageSnapshot activePage, @@ -2839,7 +2906,7 @@ void main() { pages: [page], activePage: _pageSnapshot(page, text: 'after', contentRevision: 2, lineups: [ - _editedLineup(row, (data) => data['notes'] = 'teammate notes'), + _editedLineup(row, (link) => link['notes'] = 'teammate notes'), ]), )); await _settle(); @@ -3613,32 +3680,50 @@ void main() { group('holding a lineup spot while a teammate edits the page', () { const moved = Offset(500, 500); - /// Lineup `a` from origin `a`, lineup `a2` from the same origin, and - /// lineup `b` on spots of its own, each with its origin at [at]. + /// Lineup `link-a` from origin `a` and lineup `link-a2` from the same + /// origin, one group, and lineup `link-b` on spots of its own, its own + /// group; each origin at [at]. With [teammateLineup], a teammate's + /// lineup `link-c` from origin `a` has joined `a`'s group. List lineups( RemotePage page, { Offset at = const Offset(10, 20), int revision = 1, String notes = 'remote lineup', + bool teammateLineup = false, }) => [ - _lineupRow(page.publicId, 'link-a', - origin: _originJson('a', at), - landing: _landingJson('landing-a'), - notes: notes, - revision: revision), - _lineupRow(page.publicId, 'link-a2', - origin: _originJson('a', at), - landing: _landingJson('landing-a2', const Offset(60, 60)), - notes: notes, - revision: revision, - sortIndex: 1), - _lineupRow(page.publicId, 'link-b', - origin: _originJson('b', at), - landing: _landingJson('landing-b'), - notes: notes, - revision: revision, - sortIndex: 2), + _groupRow( + page.publicId, + 'link-a', + origins: [_originJson('a', at)], + landings: [ + _landingJson('landing-a'), + _landingJson('landing-a2', const Offset(60, 60)), + if (teammateLineup) + _landingJson('landing-c', const Offset(90, 90)), + ], + links: [ + _linkJson('link-a', + originId: 'a', landingId: 'landing-a', notes: notes), + _linkJson('link-a2', + originId: 'a', landingId: 'landing-a2', notes: notes), + if (teammateLineup) + _linkJson('link-c', originId: 'a', landingId: 'landing-c'), + ], + revision: revision, + ), + _groupRow( + page.publicId, + 'link-b', + origins: [_originJson('b', at)], + landings: [_landingJson('landing-b')], + links: [ + _linkJson('link-b', + originId: 'b', landingId: 'landing-b', notes: notes), + ], + revision: revision, + sortIndex: 1, + ), ]; Future<(ProviderContainer, _FakeRemoteEditorNotifier, RemotePage)> @@ -3665,30 +3750,29 @@ void main() { Offset originAt(ProviderContainer container, String id) => container.read(lineUpProvider).originById(id)!.agent.position; - test('dragging a spot holds only the lineups on it until release', - () async { + test('dragging a spot holds only its lineup group until release', () async { final (container, remote, page) = await open(); container.read(editorPointersProvider.notifier) ..holdEntity(1, 'landing-a') ..down(1); // A teammate moves every origin, edits every lineup's notes, and adds - // a lineup to the held lineup's origin. + // a lineup to the held lineup's origin, so to its group. remote.setSnapshot(_editorSnapshot( pages: [page], - activePage: - _pageSnapshot(page, text: 'after', contentRevision: 2, lineups: [ - ...lineups(page, at: moved, revision: 2, notes: 'teammate notes'), - _lineupRow(page.publicId, 'link-c', - origin: _originJson('a', moved), - landing: _landingJson('landing-c', const Offset(90, 90)), - sortIndex: 3), - ]), + activePage: _pageSnapshot(page, + text: 'after', + contentRevision: 2, + lineups: lineups(page, + at: moved, + revision: 2, + notes: 'teammate notes', + teammateLineup: true)), )); await _settle(); - // The dragged landing's lineup waits, and so does every lineup that - // shares a spot with it: their shared origin is drawn once. + // The dragged landing's lineup waits, and so does every lineup in its + // group: the group is one row, so it takes one side whole. final lineUps = container.read(lineUpProvider); expect(container.read(textProvider).single.text, 'after'); expect(originAt(container, 'a'), const Offset(10, 20)); @@ -3707,12 +3791,12 @@ void main() { expect(released.linkById('link-a')!.notes, 'teammate notes'); expect(released.linkById('link-a2')!.notes, 'teammate notes'); expect(released.links.map((link) => link.id), - ['link-a', 'link-a2', 'link-b', 'link-c']); + ['link-a', 'link-a2', 'link-c', 'link-b']); expect(released.origins, hasLength(2)); expect(container.read(strategyOpQueueProvider).pending, isEmpty); }); - test('a placement pinned to an origin holds only that origin\'s lineups', + test('a placement pinned to an origin holds only that origin\'s group', () async { final (container, remote, page) = await open(); container.read(lineUpProvider.notifier).startFromOrigin('a'); @@ -4799,54 +4883,47 @@ void main() { }); test( - 'copies of a shared spot that disagree are drawn apart, and opening ' - 'the page writes nothing', () async { + 'a group row draws its shared spots once, opening the page writes ' + 'nothing, and an edit writes the whole group', () async { final page = _page('page-1', 0); - // A change to the shared origin reached link-1 but not link-2, and one - // to the shared landing reached link-3 but not link-2. - final rows = [ - _lineupRow(page.publicId, 'link-1', - origin: _originJson('origin', const Offset(100, 100)), - landing: _landingJson('landing-1'), - revision: 1), - _lineupRow(page.publicId, 'link-2', - origin: _originJson('origin', const Offset(200, 200)), - landing: _landingJson('landing', const Offset(220, 220)), - revision: 9, - sortIndex: 1), - _lineupRow(page.publicId, 'link-3', - origin: _originJson('origin-3'), - landing: _landingJson('landing', const Offset(330, 330)), - revision: 2, - sortIndex: 2), - ]; - final container = await openCloudPage(page, rows); + // link-1 and link-2 share an origin, link-2 and link-3 a landing: one + // group, one row. + final container = await openCloudPage(page, [ + _groupRow( + page.publicId, + 'link-1', + origins: [ + _originJson('origin', const Offset(100, 100)), + _originJson('origin-3'), + ], + landings: [ + _landingJson('landing-1'), + _landingJson('landing', const Offset(220, 220)), + ], + links: [ + _linkJson('link-1', originId: 'origin', landingId: 'landing-1'), + _linkJson('link-2', originId: 'origin', landingId: 'landing'), + _linkJson('link-3', originId: 'origin-3', landingId: 'landing'), + ], + revision: 9, + ), + ]); - // One spot per value: the value of the smallest lineup id carrying the - // spot keeps its id, the other is drawn under an alias. Each lineup is - // attached to its own copy, its marker following the alias. final lineUps = container.read(lineUpProvider); - expect(lineUps.origins.map((origin) => origin.id), - ['origin', 'origin@link-2', 'origin-3']); + expect(lineUps.origins.map((origin) => origin.id), ['origin', 'origin-3']); expect(lineUps.landings.map((landing) => landing.id), - ['landing-1', 'landing', 'landing@link-3']); + ['landing-1', 'landing']); expect([ for (final link in lineUps.links) (link.id, link.originId, link.landingId) ], [ ('link-1', 'origin', 'landing-1'), - ('link-2', 'origin@link-2', 'landing'), - ('link-3', 'origin-3', 'landing@link-3'), + ('link-2', 'origin', 'landing'), + ('link-3', 'origin-3', 'landing'), ]); expect( lineUps.originById('origin')!.agent.position, const Offset(100, 100)); - final aliasedOrigin = lineUps.originById('origin@link-2')!.agent; - expect(aliasedOrigin.position, const Offset(200, 200)); - expect(aliasedOrigin.lineUpID, 'origin@link-2'); expect(lineUps.landingById('landing')!.ability.position, const Offset(220, 220)); - final aliasedLanding = lineUps.landingById('landing@link-3')!.ability; - expect(aliasedLanding.position, const Offset(330, 330)); - expect(aliasedLanding.lineUpID, 'landing@link-3'); // Opening the page writes no lineup, nor does an unrelated edit. await _settle(); @@ -4861,29 +4938,144 @@ void main() { expect(queuedLineupOps(), isEmpty); expect(lineupOpsAfterTextEdit(container, page), isEmpty); - // An edit to link-2 writes its row with its own copies, under the real - // ids, against its own revision. + // An edit to link-2 writes the group's row, every lineup and spot in it + // under the same ids, against the group's revision. container .read(lineUpProvider.notifier) .updateLink(lineUps.linkById('link-2')!.copyWith(notes: 'jump throw')); await _settle(); - final link2 = EntitySyncKey.lineup(page.publicId, 'link-2'); - expect(queuedLineupOps().keys, [link2]); - final patch = queuedLineupOps()[link2]! as LineupPatchOp; + final group = EntitySyncKey.lineup(page.publicId, 'link-1'); + expect(queuedLineupOps().keys, [group]); + final patch = queuedLineupOps()[group]! as LineupPatchOp; expect(patch.expectedLineupRevision, 9); final data = cloudPayloadData(patch.payload); - expect(data['notes'], 'jump throw'); - final origin = data['origin'] as Map; - expect(origin['id'], 'origin'); + expect(data['id'], 'link-1'); + expect(_entries(data, 'links').map((link) => link['id']), + ['link-1', 'link-2', 'link-3']); + expect(_entry(data, 'links', 'link-2')['notes'], 'jump throw'); + expect(_entry(data, 'links', 'link-1')['notes'], 'remote lineup'); + final origin = _entry(data, 'origins', 'origin'); expect((origin['agent'] as Map)['lineUpID'], 'origin'); - expect((origin['agent'] as Map)['position'], {'dx': 200.0, 'dy': 200.0}); - final landing = data['landing'] as Map; - expect(landing['id'], 'landing'); + expect((origin['agent'] as Map)['position'], {'dx': 100.0, 'dy': 100.0}); + final landing = _entry(data, 'landings', 'landing'); expect((landing['ability'] as Map)['lineUpID'], 'landing'); expect((landing['ability'] as Map)['position'], {'dx': 220.0, 'dy': 220.0}); await _settle(); }); + test( + 'a page duplicated before cloud sync writes its lineups to its own ' + 'group rows, never the other page\'s', () async { + final one = _page('page-1', 0); + final two = _page('page-2', 1); + // Page 2 repeats page 1's lineup ids. A group's id is unique in the + // strategy, so its group took another id. It also has a group a + // teammate deleted, named after a lineup id. + final oneSnapshot = + _pageSnapshot(one, lineups: [_lineup(one.publicId, 'a')]); + final twoSnapshot = _pageSnapshot(two, lineups: [ + _groupRow( + two.publicId, + 'dup-group', + origins: [_originJson('a', const Offset(50, 50))], + landings: [_landingJson('landing-a')], + links: [_linkJson('link-a', originId: 'a', landingId: 'landing-a')], + ), + _groupRow( + two.publicId, + 'link-b', + origins: [_originJson('b')], + landings: [_landingJson('landing-b')], + links: [_linkJson('link-b', originId: 'b', landingId: 'landing-b')], + revision: 2, + sortIndex: 1, + deleted: true, + ), + ]); + final remote = _FakeRemoteEditorNotifier( + _editorSnapshot(pages: [one, two], activePage: oneSnapshot), + pageCatalog: {one.publicId: oneSnapshot, two.publicId: twoSnapshot}, + ); + final queue = _FakeStrategyOpQueueNotifier(); + final container = await _cloudContainer(remote: remote, queue: queue); + final session = container.read(strategyPageSessionProvider.notifier); + await session.initializeForStrategy( + strategyId: 'cloud-strategy', + source: StrategySource.cloud, + selectFirstPageIfNeeded: true, + ); + final liveSync = container.read(activePageLiveSyncProvider.notifier); + Map lineupOps() => { + for (final MapEntry(:key, :value) + in queue.state.queuedByEntityKey.entries) + if (key.kind == EntitySyncKeyKind.lineup) key: value.pending.op, + }; + void editNotes(String notes) { + container.read(lineUpProvider.notifier).updateLink(container + .read(lineUpProvider) + .linkById('link-a')! + .copyWith(notes: notes)); + } + + final oneKey = EntitySyncKey.lineup(one.publicId, 'link-a'); + final twoKey = EntitySyncKey.lineup(two.publicId, 'dup-group'); + + // An edit on page 1 writes page 1's group. + editNotes('on one'); + await _settle(); + expect(lineupOps().keys, [oneKey]); + + // On page 2, the same lineup id is page 2's lineup, in page 2's group. + await session.setActivePage(two.publicId); + await _settle(); + expect(container.read(lineUpProvider).originById('a')!.agent.position, + const Offset(50, 50)); + editNotes('on two'); + await _settle(); + expect(lineupOps().keys, unorderedEquals([oneKey, twoKey])); + final patch = lineupOps()[twoKey]! as LineupPatchOp; + expect(cloudPayloadData(patch.payload)['id'], 'dup-group'); + expect(_onlyLinkIn(patch.payload)['notes'], 'on two'); + expect(_onlyLinkIn(lineupOps()[oneKey]!.payload as CloudPayload)['notes'], + 'on one'); + expect(liveSync.lineupGroupOf(one.publicId, 'link-a'), 'link-a'); + expect(liveSync.lineupGroupOf(two.publicId, 'link-a'), 'dup-group'); + + // Back on page 1, its lineup still writes page 1's group. + await session.setActivePage(one.publicId); + await _settle(); + expect(container.read(lineUpProvider).originById('a')!.agent.position, + const Offset(10, 20)); + editNotes('on one again'); + await _settle(); + expect(_onlyLinkIn(lineupOps()[oneKey]!.payload as CloudPayload)['notes'], + 'on one again'); + expect(lineupOps().keys, unorderedEquals([oneKey, twoKey])); + + // On page 2, lineups added under an id a group this page deleted is + // named after (an old copy brought back, say) start a new group under + // another id, never landing on the teammate's deleted group. + await session.setActivePage(two.publicId); + await _settle(); + container.read(lineUpProvider.notifier).addRecovered( + lineUpGraphFromRemoteLineups([_lineup(two.publicId, 'b')])); + final desired = liveSync.syncLocalPage( + strategyPublicId: 'cloud-strategy', + pageId: two.publicId, + )!; + final added = { + for (final MapEntry(:key, :value) in desired.entries) + if (key.kind == EntitySyncKeyKind.lineup && value is LineupAddOp) + key: value, + }; + final newKey = added.keys.single; + expect(newKey.pageId, two.publicId); + expect(newKey.entityId, isNot(anyOf('link-b', 'link-a', 'dup-group'))); + expect(added[newKey]!.expectedLineupRevision, isNull); + expect(_onlyLinkIn(added[newKey]!.payload)['id'], 'link-b'); + await _settle(); + }); + test('a Paranoia saved at the old size opens corrected and sends nothing', () async { final page = _page('page-1', 0); @@ -5153,18 +5345,32 @@ void main() { entry.key: entry.value, }; - /// The stored row of lineup [id] in [lineups]. - RemoteLineup rowOf(List lineups, String id) => - lineups.singleWhere((row) => row.publicId == id); - + /// The key of lineup group [id]'s row. EntitySyncKey keyOf(RemotePage page, String id) => EntitySyncKey.lineup(page.publicId, id); - /// Every row the server holds reads back as the lineup it is keyed by. + /// The group lineup or spot [id] was last drawn from or written to. + String groupOf(ProviderContainer container, RemotePage page, String id) => + container + .read(activePageLiveSyncProvider.notifier) + .lineupGroupOf(page.publicId, id)!; + + /// The id a group of lineups [ids], new together, is named after. + String firstOf(Iterable ids) => + ids.reduce((a, b) => a.compareTo(b) <= 0 ? a : b); + + /// Every row the server holds reads back whole: each lineup in it is + /// drawn, from that row. void expectEveryRowDrawn(List lineups) { + final drawn = lineUpGraphFromCloudRows( + [for (final row in lineups) CloudLineupRow.remote(row)]); expect( - lineUpGraphFromRemoteLineups(lineups).links.map((link) => link.id), - [for (final row in lineups) row.publicId], + {for (final link in drawn.graph.links) link.id: drawn.groupOf[link.id]}, + { + for (final MapEntry(key: group, value: held) + in _groupsOf(lineups).entries) + for (final id in held['links']!) id: group, + }, ); } @@ -5210,7 +5416,7 @@ void main() { return server.liveRows; } - /// Every lineup write is one whole lineup row, keyed by its link. + /// Every lineup write is one whole group row, keyed by its group. void expectLineupRows(Map ops) { for (final op in ops.values) { final payload = switch (op) { @@ -5219,7 +5425,7 @@ void main() { _ => null, }; if (payload == null) continue; - expect(payload['kind'], cloudLineupPayloadKind, reason: '$op'); + expect(payload['kind'], cloudLineupsPayloadKind, reason: '$op'); expect(cloudPayloadData(payload)['id'], op.entityPublicId, reason: '$op'); } @@ -5282,10 +5488,8 @@ void main() { await land(container, remote, page, contentRevision: 3); // Then a teammate arms the same origin. - server.teammateEdit(link.id, (data) { - final origin = data['origin'] as Map; - (origin['agent'] as Map)['weapon'] = 'vandal'; - }); + server.teammateEdit( + link.id, _eachAgent((agent) => agent['weapon'] = 'vandal')); remote.setSnapshot(serverSnapshot(page, server.rows, 4)); await settleSync(container, 4); expect( @@ -5300,7 +5504,8 @@ void main() { expect(origin.agent.weapon, WeaponType.vandal); final patch = desiredOps(container, page).values.whereType().single; - final sent = cloudPayloadData(patch.payload)['origin'] as Map; + final sent = + _entry(cloudPayloadData(patch.payload), 'origins', link.originId); expect((sent['agent'] as Map)['weapon'], 'vandal'); await _settle(); }); @@ -5377,16 +5582,6 @@ void main() { (link.id, link.originId, link.landingId, link.name), ]; - /// The ids of the spots [lineups]' rows carry, by the lineup carrying - /// them. - Map spotsOf(List lineups) => { - for (final row in lineups) - row.publicId: ( - (cloudPayloadData(row.payload)['origin'] as Map)['id'] as String, - (cloudPayloadData(row.payload)['landing'] as Map)['id'] as String, - ), - }; - test('a fan-in lineup keeps its shared landing, ids and names', () async { final (container, remote, page) = await openEmpty(); final (a, b) = placeFanIn(container); @@ -5395,11 +5590,17 @@ void main() { final lineups = await land(container, remote, page, contentRevision: 2); - // One row per lineup, each carrying its own copy of the landing. - expect(spotsOf(lineups), { - a.id: (a.originId, a.landingId), - b.id: (b.originId, a.landingId), + // One row for the group, holding the landing once, named after its + // smaller lineup id. + final group = firstOf([a.id, b.id]); + expect(_groupsOf(lineups), { + group: { + 'origins': [a.originId, b.originId], + 'landings': [a.landingId], + 'links': [a.id, b.id], + }, }); + expect(groupOf(container, page, b.id), group); // Hydration draws the shared landing once. final hydrated = container.read(lineUpProvider); @@ -5424,9 +5625,12 @@ void main() { final lineups = await land(container, remote, page, contentRevision: 2); - expect(spotsOf(lineups), { - a.id: (a.originId, a.landingId), - b.id: (a.originId, b.landingId), + expect(_groupsOf(lineups), { + firstOf([a.id, b.id]): { + 'origins': [a.originId], + 'landings': [a.landingId, b.landingId], + 'links': [a.id, b.id], + }, }); final hydrated = container.read(lineUpProvider); @@ -5441,19 +5645,19 @@ void main() { await _settle(); }); - test('moving a shared spot patches every lineup on it and nothing else', - () async { + test('moving a shared spot patches its group and nothing else', () async { final (container, remote, page) = await openEmpty(); final (a, b) = placeFanIn(container); final other = place(container); var lineups = await land(container, remote, page, contentRevision: 2); - // A's row moves on to revision 2; B's and the other's stay at 1. + final group = firstOf([a.id, b.id]); + // The group's row moves on to revision 2; the other's stays at 1. container.read(lineUpProvider.notifier).updateLink( container.read(lineUpProvider).linkById(a.id)!.copyWith(notes: 'x')); lineups = await land(container, remote, page, contentRevision: 3); expect( {for (final row in lineups) row.publicId: row.revision}, - {a.id: 2, b.id: 1, other.id: 1}, + {group: 2, other.id: 1}, ); const moved = Offset(320, 340); @@ -5461,32 +5665,34 @@ void main() { .read(lineUpProvider.notifier) .updateLandingPosition(a.landingId, moved); - // One patch per lineup on the landing, each checked against its own - // row's revision, each carrying the moved landing. + // One patch, of the group, against its revision, carrying the moved + // landing once for both its lineups. final ops = desiredOps(container, page); expectLineupRows(ops); - expect(ops.keys.toSet(), {keyOf(page, a.id), keyOf(page, b.id)}); - final patches = ops.values.cast(); - expect( - { - for (final op in patches) op.lineupPublicId: op.expectedLineupRevision - }, - {a.id: 2, b.id: 1}, - ); - for (final op in patches) { - final landing = cloudPayloadData(op.payload)['landing'] as Map; - expect((landing['ability'] as Map)['position'], - {'dx': moved.dx, 'dy': moved.dy}); - } + expect(ops.keys, [keyOf(page, group)]); + final patch = ops.values.single as LineupPatchOp; + expect(patch.expectedLineupRevision, 2); + final landing = + _entry(cloudPayloadData(patch.payload), 'landings', a.landingId); + expect((landing['ability'] as Map)['position'], + {'dx': moved.dx, 'dy': moved.dy}); lineups = await land(container, remote, page, contentRevision: 4); expect( {for (final row in lineups) row.publicId: row.revision}, - {a.id: 3, b.id: 2, other.id: 1}, + {group: 3, other.id: 1}, ); + Offset landingAt(RemoteLineup row) { + final ability = _entries(cloudPayloadData(row.payload), 'landings') + .single['ability'] as Map; + final position = ability['position'] as Map; + return Offset((position['dx'] as num).toDouble(), + (position['dy'] as num).toDouble()); + } + expect( - {for (final row in lineups) row.publicId: _endsOf(row).landingAt}, - {a.id: moved, b.id: moved, other.id: const Offset(300, 300)}, + {for (final row in lineups) row.publicId: landingAt(row)}, + {group: moved, other.id: const Offset(300, 300)}, ); final hydrated = container.read(lineUpProvider); expect(hydrated.landingById(a.landingId)!.ability.position, moved); @@ -5499,9 +5705,17 @@ void main() { final (container, remote, page) = await openEmpty(); final (a, b) = placeFanIn(container); var lineups = await land(container, remote, page, contentRevision: 2); + final group = firstOf([a.id, b.id]); container.read(lineUpProvider.notifier).deleteOrigin(a.originId); lineups = await land(container, remote, page, contentRevision: 3); - expect(spotsOf(lineups), {b.id: (b.originId, a.landingId)}); + // The group keeps its row, now holding B alone. + expect(_groupsOf(lineups), { + group: { + 'origins': [b.originId], + 'landings': [a.landingId], + 'links': [b.id], + }, + }); container.read(actionProvider.notifier).undoAction(); @@ -5511,21 +5725,18 @@ void main() { (b.id, b.originId, a.landingId, 'From mid'), (a.id, a.originId, a.landingId, 'From heaven'), ]); + // A patch of the group's row, expecting its revision. final ops = desiredOps(container, page); expectLineupRows(ops); - expect(ops.keys, [keyOf(page, a.id)]); - // An add over the tombstone the delete left, expecting its revision. - expect(server.row(a.id).deleted, isTrue); + expect(ops.keys, [keyOf(page, group)]); expect( - (ops.values.single as LineupAddOp).expectedLineupRevision, - server.row(a.id).revision, + (ops.values.single as LineupPatchOp).expectedLineupRevision, + server.row(group).revision, ); lineups = await land(container, remote, page, contentRevision: 4); - expect(spotsOf(lineups), { - b.id: (b.originId, a.landingId), - a.id: (a.originId, a.landingId), - }); - expect(server.row(a.id).revision, 3); + expect(_groupsOf(lineups)[group]!['links'], [b.id, a.id]); + expect(_groupsOf(lineups)[group]!['landings'], [a.landingId]); + expect(server.row(group).revision, 3); expect(linkShape(container.read(lineUpProvider)), hasLength(2)); await _settle(); }); @@ -5536,6 +5747,7 @@ void main() { final (container, remote, page) = await openEmpty(); final (a, b) = placeFanIn(container); var lineups = await land(container, remote, page, contentRevision: 2); + final group = firstOf([a.id, b.id]); container.read(lineUpProvider.notifier).deleteLink(a.id); @@ -5543,66 +5755,81 @@ void main() { var graph = container.read(lineUpProvider).graph; expect(graph.origins.map((origin) => origin.id), [b.originId]); expect(graph.landings.map((landing) => landing.id), [a.landingId]); - // Only A's row is deleted; B's row, landing copy and all, is left be. + // The group's row is patched to hold B alone. final ops = desiredOps(container, page); - expect(ops.keys, [keyOf(page, a.id)]); - expect(ops.values.single, isA()); + expect(ops.keys, [keyOf(page, group)]); + expect(ops.values.single, isA()); lineups = await land(container, remote, page, contentRevision: 3); - expect(spotsOf(lineups), {b.id: (b.originId, a.landingId)}); + expect(_groupsOf(lineups), { + group: { + 'origins': [b.originId], + 'landings': [a.landingId], + 'links': [b.id], + }, + }); graph = container.read(lineUpProvider).graph; expect(graph.links.map((link) => link.id), [b.id]); expect(graph.landings.map((landing) => landing.id), [a.landingId]); - // Deleting the last lineup on the landing takes the landing too. + // Deleting the group's last lineup deletes its row, landing and all. container.read(lineUpProvider.notifier).deleteLink(b.id); graph = container.read(lineUpProvider).graph; expect(graph.origins, isEmpty); expect(graph.landings, isEmpty); + expect(desiredOps(container, page).values.single, isA()); lineups = await land(container, remote, page, contentRevision: 4); expect(lineups, isEmpty); + expect(server.row(group).deleted, isTrue); expect(container.read(lineUpProvider).graph.landings, isEmpty); await _settle(); }); - test('a teammate editing the other link of a fan-in keeps both edits', - () async { + test( + 'a teammate editing the other lineup of a group first makes my edit ' + 'a conflict that overwrites neither', () async { final (container, remote, page) = await openEmpty(); final (a, b) = placeFanIn(container); await land(container, remote, page, contentRevision: 2); + final group = firstOf([a.id, b.id]); + final key = keyOf(page, group); container .read(lineUpProvider.notifier) .updateLink(container.read(lineUpProvider).linkById(a.id)!.copyWith( notes: 'jump throw', )); await _settle(); - // Only A's row is written, checked against A's own revision, so a - // teammate's write to B's row cannot conflict with it. + // The group's row is written, against the revision both started from. final mine = queuedOps(); expect( mine.keys.where((key) => key.kind == EntitySyncKeyKind.lineup), - [keyOf(page, a.id)], - ); - expect( - (mine[keyOf(page, a.id)]! as LineupPatchOp).expectedLineupRevision, - 1, + [key], ); + expect((mine[key]! as LineupPatchOp).expectedLineupRevision, 1); - await land( - container, - remote, - page, - contentRevision: 3, - teammate: (data) { - if (data['id'] == b.id) data['notes'] = 'teammate notes'; - }, - ); + // A teammate's edit to B, in the same group, lands first. + server.teammateEdit(group, + (data) => _entry(data, 'links', b.id)['notes'] = 'teammate notes'); + send(remote, page, queuedOps(), contentRevision: 3); + for (var i = 0; i < 10; i++) { + await _settle(); + } - final hydrated = container.read(lineUpProvider); - expect(hydrated.linkById(a.id)!.notes, 'jump throw'); - expect(hydrated.linkById(b.id)!.notes, 'teammate notes'); - expect(hydrated.landings.single.id, a.landingId); - expect(desiredOps(container, page), isEmpty); - expect(queue.state.attentionByEntityKey, isEmpty); + // Refused: the server keeps the teammate's version, and mine waits. + expect( + queue.state.lastAckBatch + .singleWhere((acked) => acked.entityKey == key) + .ack, + isA().having((ack) => ack.rejectionReason, 'reason', + OpRejectionReason.revisionMismatch), + ); + final stored = server.row(group); + expect(stored.revision, 2); + expect(_linkIn(stored.payload, b.id)['notes'], 'teammate notes'); + expect(_linkIn(stored.payload, a.id)['notes'], ''); + expect( + container.read(lineUpProvider).linkById(a.id)!.notes, 'jump throw'); + expect(queue.state.attentionByEntityKey.keys, [key]); + expect(container.read(strategyConflictProvider), hasLength(1)); await _settle(); }); @@ -5619,7 +5846,8 @@ void main() { expect(mine.expectedLineupRevision, 1); // A teammate's notes edit to the same lineup lands first. - server.teammateEdit(link.id, (data) => data['notes'] = 'theirs'); + server.teammateEdit( + link.id, _eachLink((link) => link['notes'] = 'theirs')); remote.setSnapshot(serverSnapshot(page, server.rows, 3)); await _settle(); // Still checked against the version the user saw: never re-based onto @@ -5639,7 +5867,7 @@ void main() { isA().having((ack) => ack.rejectionReason, 'reason', OpRejectionReason.revisionMismatch), ); - expect(cloudPayloadData(server.row(link.id).payload)['notes'], 'theirs'); + expect(_onlyLinkIn(server.row(link.id).payload)['notes'], 'theirs'); expect(container.read(lineUpProvider).links.single.notes, 'mine'); expect(queue.state.needsAttention, isTrue); expect(queue.state.attentionByEntityKey.keys, [key]); @@ -5660,58 +5888,66 @@ void main() { }); test( - 'deleting a spot while a teammate adds a lineup to it keeps ' - 'theirs whole', () async { + 'deleting a spot while a teammate adds a lineup to its group is ' + 'refused, and theirs stays whole', () async { final (container, remote, page) = await openEmpty(); final first = place(container); - final landed = await land(container, remote, page, contentRevision: 2); - - // A teammate's lineup from the same origin, carrying their own copy - // of it, lands; this canvas has not drawn it yet. - server.teammateAdd(RemoteLineup( - publicId: 'their-link', - strategyPublicId: 'cloud-strategy', - pagePublicId: page.publicId, - payload: cloudLineupPayload({ - ...cloudPayloadData(rowOf(landed, first.id).payload), - 'id': 'their-link', - 'name': 'Theirs', - 'landing': _landingJson('their-landing', const Offset(80, 80)), - }), - sortIndex: 10, - revision: 1, - deleted: false, - )); + await land(container, remote, page, contentRevision: 2); + final key = keyOf(page, first.id); + + // A teammate's lineup from the same origin lands in the origin's + // group; this canvas has not drawn it yet. + server.teammateEdit(first.id, (data) { + (data['landings'] as List) + .add(_landingJson('their-landing', const Offset(80, 80))); + (data['links'] as List).add(_linkJson('their-link', + originId: first.originId, + landingId: 'their-landing', + name: 'Theirs')); + }); - // The user deletes the origin, which takes their only lineup on it. + // The user deletes the origin, which takes their only lineup on it: + // the group, as they see it, is gone. container.read(lineUpProvider.notifier).deleteOrigin(first.originId); await _settle(); final sent = queuedOps(); - expect(lineupOps(sent).keys, [keyOf(page, first.id)]); + expect(lineupOps(sent).keys, [key]); expect(lineupOps(sent).values.single, isA()); - // The server deletes the user's row; the teammate's row is untouched. + // The server refuses the delete: the group changed since. Their + // lineup is untouched and the user's delete waits. send(remote, page, sent, contentRevision: 3); - await settleSync(container, 3); - expect(server.row(first.id).deleted, isTrue); - expect(server.row('their-link').revision, 1); + for (var i = 0; i < 10; i++) { + await _settle(); + } + expect(server.row(first.id).deleted, isFalse); + expect(server.row(first.id).revision, 2); + expect(queue.state.attentionByEntityKey.keys, [key]); - // Their lineup is drawn whole, on the origin from its own row. + // Taking the cloud's version draws their lineup whole, on the origin. + final resolved = await container + .read(strategyPageSessionProvider.notifier) + .useCloudVersionsForRejected(); + await _settle(); + expect(resolved, isTrue); final graph = container.read(lineUpProvider).graph; - expect(graph.links.map((link) => link.id), ['their-link']); + expect(graph.links.map((link) => link.id), [first.id, 'their-link']); expect(graph.origins.map((origin) => origin.id), [first.originId]); expect(graph.origins.single.agent.position, placedAt); - expect(graph.landings.map((landing) => landing.id), ['their-landing']); + expect(graph.landings.map((landing) => landing.id), + [first.landingId, 'their-landing']); expect(queue.state.attentionByEntityKey, isEmpty); expect(desiredOps(container, page), isEmpty); await _settle(); }); - test('a lineup placed from an origin a teammate deleted saves whole', - () async { + test( + 'a lineup placed from an origin whose group a teammate deleted ' + 'meanwhile waits in attention, nothing lost', () async { final (container, remote, page) = await openEmpty(); final first = place(container); await land(container, remote, page, contentRevision: 2); + final key = keyOf(page, first.id); // The user starts a new lineup from the landed origin... final lineUps = container.read(lineUpProvider.notifier) @@ -5721,34 +5957,47 @@ void main() { data: AgentData.agents[AgentType.sova]!.abilities[2], position: const Offset(500, 500), )); - // ...while a teammate deletes that lineup, origin included. + // ...while a teammate deletes that lineup's group, origin included. + // The placement holds the group, so the canvas keeps it as it was. server.teammateDelete(first.id); remote.setSnapshot(serverSnapshot(page, server.rows, 3)); await _settle(); - // The teammate's delete stands: the user never re-sends it. - expect( - desiredOps(container, page), isNot(contains(keyOf(page, first.id)))); + expect(container.read(lineUpProvider).linkById(first.id), isNotNull); + expect(desiredOps(container, page), isNot(contains(key))); final second = lineUps.commitPlacement()!; await _settle(); - // The new lineup's row carries its own copy of the origin, so it - // saves whole without the deleted lineup. + // The new lineup joins the origin's group: the group's row, as the user + // has it, against the revision they saw, not the teammate's tombstone. final queued = lineupOps(queuedOps()); - expect(queued.keys, [keyOf(page, second.id)]); - final add = queued.values.single as LineupAddOp; + expect(groupOf(container, page, second.id), first.id); + expect(queued.keys, [key]); + final patch = queued.values.single as LineupPatchOp; + expect(patch.expectedLineupRevision, 1); + final data = cloudPayloadData(patch.payload); + expect(_entries(data, 'origins').single['id'], first.originId); + expect(_entries(data, 'links').map((link) => link['id']), + [first.id, second.id]); + + // The server refuses it as deleted: the teammate's delete stands, and + // the user's lineups wait, still on screen, for them to choose. + send(remote, page, queuedOps(), contentRevision: 4); + for (var i = 0; i < 10; i++) { + await _settle(); + } expect( - (cloudPayloadData(add.payload)['origin'] as Map)['id'], - first.originId, + queue.state.lastAckBatch + .singleWhere((acked) => acked.entityKey == key) + .ack, + isA().having( + (ack) => ack.rejectionReason, 'reason', OpRejectionReason.deleted), ); - - // The server holds the new lineup whole, and it loads back. - final stored = await land(container, remote, page, contentRevision: 4); - expect(stored.map((row) => row.publicId), [second.id]); expect(server.row(first.id).deleted, isTrue); - final hydrated = container.read(lineUpProvider).graph; - expect(hydrated.links.map((link) => link.id), [second.id]); - expect(hydrated.origins.map((origin) => origin.id), [first.originId]); + expect(server.liveRows, isEmpty); + expect(queue.state.attentionByEntityKey.keys, [key]); + expect(container.read(lineUpProvider).links.map((link) => link.id), + [first.id, second.id]); await _settle(); }); @@ -5839,11 +6088,11 @@ void main() { remote, page, contentRevision: 3, - teammate: (data) { - data['images'] = [ + teammate: _eachLink((link) { + link['images'] = [ {'id': 'teammate-image', 'fileExtension': '.png'}, ]; - }, + }), ); expect( container.read(lineUpProvider).links.single.images.map((i) => i.id), @@ -5857,7 +6106,7 @@ void main() { expect(restored.images.map((image) => image.id), ['teammate-image']); final patch = desiredOps(container, page).values.whereType().single; - final item = cloudPayloadData(patch.payload); + final item = _linkIn(patch.payload, link.id); expect(patch.lineupPublicId, link.id); expect(item['notes'], ''); expect( @@ -5906,7 +6155,7 @@ void main() { final patch = desiredOps(container, page).values.whereType().single; expect( - (cloudPayloadData(patch.payload)['images'] as List) + (_onlyLinkIn(patch.payload)['images'] as List) .map((image) => (image as Map)['id']), ['a', 'b', 'c', 'd'], ); @@ -5938,10 +6187,10 @@ void main() { remote, page, contentRevision: 3, - teammate: (data) { - (data['images'] as List) + teammate: _eachLink((link) { + (link['images'] as List) .insert(1, {'id': 'x', 'fileExtension': '.png'}); - }, + }), ); expect(imageIds(container), ['a', 'x', 'c']); final history = container.read(actionProvider.notifier); @@ -5970,11 +6219,9 @@ void main() { remote, page, contentRevision: 3, - teammate: (data) { - final landing = data['landing'] as Map; - final ability = landing['ability'] as Map; + teammate: _eachAbility((ability) { (ability['visualState'] as Map)['showRangeFill'] = false; - }, + }), ); container.read(actionProvider.notifier).undoAction(); @@ -6004,10 +6251,7 @@ void main() { remote, page, contentRevision: 3, - teammate: (data) { - final origin = data['origin'] as Map; - (origin['agent'] as Map)['weapon'] = 'vandal'; - }, + teammate: _eachAgent((agent) => agent['weapon'] = 'vandal'), ); final origin = container.read(lineUpProvider).originById(link.originId)!; @@ -6065,10 +6309,8 @@ void main() { ops = queuedOps(); expect(ops.keys.where((key) => key.pageId == first.publicId), isNotEmpty); acks = server.applyBatch(ops.values); - server.teammateEdit(link.id, (data) { - final origin = data['origin'] as Map; - (origin['agent'] as Map)['weapon'] = 'vandal'; - }); + server.teammateEdit( + link.id, _eachAgent((agent) => agent['weapon'] = 'vandal')); remote.pageCatalog[first.publicId] = _pageSnapshot( first, settings: settingsFor(3), @@ -6095,121 +6337,26 @@ void main() { await _settle(); }); - /// Places lineups A and B from one origin, then C from it too, the way - /// the editor does. - List placeFanOutOfThree(ProviderContainer container) { - final (a, b) = placeFanOut(container); - final lineUps = container.read(lineUpProvider.notifier) - ..startFromOrigin(a.originId); - lineUps.setDraftAbility(PlacedAbility( - id: 'ability-z', - data: AgentData.agents[AgentType.sova]!.abilities[2], - position: const Offset(600, 450), - )); - final c = lineUps.commitPlacement(name: 'Deep')!; - return [a, b, c]; - } - - test( - 'a shared spot moved on only some rows stays queued for the rest ' - 'until it lands', () async { - final (container, remote, page) = await openEmpty(); - final [a, b, c] = placeFanOutOfThree(container); - await land(container, remote, page, contentRevision: 2); - expect( - {for (final row in server.liveRows) row.publicId: row.revision}, - {a.id: 1, b.id: 1, c.id: 1}, - ); - - const moved = Offset(500, 500); - container - .read(lineUpProvider.notifier) - .updateOriginAgentPosition(a.originId, moved); - await _settle(); - final sent = lineupOps(queuedOps()); - final cKey = keyOf(page, c.id); - expect(sent.keys.toSet(), {keyOf(page, a.id), keyOf(page, b.id), cKey}); - - // The server takes A's and B's patches; C's send fails, so it waits in - // the queue while the session refreshes from the server. - send(remote, page, {...sent}..remove(cKey), contentRevision: 3); - for (var i = 0; i < 10; i++) { - await _settle(); - } - // The rows disagree now: C's still holds the old origin. - expect( - { - for (final row in server.liveRows) row.publicId: _endsOf(row).originAt - }, - {a.id: moved, b.id: moved, c.id: placedAt}, - ); - - // C's patch is not taken as done by the refresh: it is still queued, - // the same op, until C's row holds the moved origin. - expect(lineupOps(queuedOps()).keys, [cKey]); - expect(queuedOps()[cKey]!.opId, sent[cKey]!.opId); - // Meanwhile the user's canvas keeps the move on all three lineups. - final onScreen = container.read(lineUpProvider); - expect(onScreen.origins.map((origin) => origin.id), [a.originId]); - expect(onScreen.origins.single.agent.position, moved); - - // The user carries on, so the page syncs against that snapshot. C's - // patch still waits, against C's revision, under the origin's id. - container.read(lineUpProvider.notifier).updateLink(container - .read(lineUpProvider) - .linkById(a.id)! - .copyWith(notes: 'next')); - await _settle(); - final waiting = lineupOps(queuedOps()); - expect(waiting.keys.toSet(), {keyOf(page, a.id), cKey}); - final patch = waiting[cKey]! as LineupPatchOp; - expect(patch.expectedLineupRevision, 1); - final origin = cloudPayloadData(patch.payload)['origin'] as Map; - expect(origin['id'], a.originId); - expect((origin['agent'] as Map)['position'], - {'dx': moved.dx, 'dy': moved.dy}); - - // Its retry lands: every row holds the moved origin. - await land(container, remote, page, contentRevision: 4); - expect( - {for (final row in server.liveRows) row.publicId: row.revision}, - {a.id: 3, b.id: 2, c.id: 2}, - ); - for (final row in server.liveRows) { - expect(_endsOf(row).origin, a.originId, reason: row.publicId); - expect(_endsOf(row).originAt, moved, reason: row.publicId); - } - // Three adds, three patches of the origin, and the notes; nothing more. - expect(server.received, hasLength(7)); - expect(lineupOps(queuedOps()), isEmpty); - expect(desiredOps(container, page), isEmpty); - expect(queue.state.attentionByEntityKey, isEmpty); - - // The rows agree again, so the origin is drawn as one spot. - final hydrated = container.read(lineUpProvider); - expect(hydrated.origins.map((origin) => origin.id), [a.originId]); - expect(hydrated.origins.single.agent.position, moved); - expect(hydrated.links.map((link) => link.originId).toSet(), {a.originId}); - await _settle(); - }); - const newer = Offset(330, 330); - const older = Offset(220, 220); - - /// Lineups A and B into one landing whose rows disagree: A's copy is at - /// [newer], B's at [older]. B's row has the higher revision, which ranks - /// nothing. - List divergedFanIn(String pageId) => [ - _lineupRow(pageId, 'link-a', - origin: _originJson('origin-a'), - landing: _landingJson('landing', newer), - revision: 1), - _lineupRow(pageId, 'link-b', - origin: _originJson('origin-b', const Offset(60, 20)), - landing: _landingJson('landing', older), - revision: 7, - sortIndex: 1), - ]; + + /// Lineups A ('Heaven') and B ('Mid') from their own origins into one + /// landing at [newer]: one group, its row `link-a` at revision 7. + RemoteLineup fanIn(String pageId) => _groupRow( + pageId, + 'link-a', + origins: [ + _originJson('origin-a'), + _originJson('origin-b', const Offset(60, 20)), + ], + landings: [_landingJson('landing', newer)], + links: [ + _linkJson('link-a', + originId: 'origin-a', landingId: 'landing', name: 'Heaven'), + _linkJson('link-b', + originId: 'origin-b', landingId: 'landing', name: 'Mid'), + ], + revision: 7, + ); /// Each lineup drawn, with the spot ids it is attached to. List<(String, String, String)> drawnLinks(ProviderContainer container) => [ @@ -6217,21 +6364,8 @@ void main() { (link.id, link.originId, link.landingId), ]; - Offset? landingAt(ProviderContainer container, String id) => - container.read(lineUpProvider).landingById(id)?.ability.position; - - test('opening a page whose copies agree writes no lineup', () async { - final (container, remote, page) = await openEmpty([ - _lineupRow('page-1', 'link-a', - origin: _originJson('origin-a'), - landing: _landingJson('landing', newer), - revision: 3), - _lineupRow('page-1', 'link-b', - origin: _originJson('origin-b', const Offset(60, 20)), - landing: _landingJson('landing', newer), - revision: 8, - sortIndex: 1), - ]); + test('opening a page with a shared landing writes no lineup', () async { + final (container, remote, page) = await openEmpty([fanIn('page-1')]); await _settle(); expect(container.read(lineUpProvider).landings.map((l) => l.id), ['landing']); @@ -6248,182 +6382,17 @@ void main() { expect(server.received, isEmpty); }); - test( - 'opening a page whose copies disagree writes nothing and draws each ' - 'copy as its own spot', () async { - final (container, remote, page) = await openEmpty([ - ...divergedFanIn('page-1'), - _lineupRow('page-1', 'link-c', - origin: _originJson('origin-c', const Offset(90, 20)), - landing: _landingJson('landing', older), - revision: 4, - sortIndex: 2), - _lineupRow('page-1', 'link-d', - origin: _originJson('origin-d', const Offset(10, 90)), - landing: _landingJson('landing-d'), - sortIndex: 3), - ]); - void expectSplit() { - // A's copy, carried by the smallest lineup id, keeps the landing's - // id; B's and C's, which agree, share one aliased spot. - expect(container.read(lineUpProvider).landings.map((l) => l.id), - ['landing', 'landing@link-b', 'landing-d']); - expect(landingAt(container, 'landing'), newer); - expect(landingAt(container, 'landing@link-b'), older); - expect(drawnLinks(container), [ - ('link-a', 'origin-a', 'landing'), - ('link-b', 'origin-b', 'landing@link-b'), - ('link-c', 'origin-c', 'landing@link-b'), - ('link-d', 'origin-d', 'landing-d'), - ]); - } + /// The live read of the page [openOnRealQueue] opened. + late _FakeRemoteEditorNotifier liveRead; - expectSplit(); - await _settle(); - expect(lineupOps(queuedOps()), isEmpty); - - // A teammate's unrelated change brings a fresh hydration: the same - // spots are drawn, and still nothing is written. - server.teammateEdit('link-d', (data) => data['notes'] = 'teammate'); - remote.setSnapshot(serverSnapshot(page, server.rows, 2)); - await settleSync(container, 2); - expect( - container.read(lineUpProvider).linkById('link-d')!.notes, 'teammate'); - expectSplit(); - expect(lineupOps(queuedOps()), isEmpty); - expect(desiredOps(container, page), isEmpty); - expect(server.received, isEmpty); - }); - - for (final (deleted, kept, keptAt) in [ - ('link-a', 'link-b', older), - ('link-b', 'link-a', newer), - ]) { - test( - 'deleting $deleted from a landing whose copies disagree leaves ' - '$kept on its own copy, its row untouched', () async { - final (container, remote, page) = - await openEmpty(divergedFanIn('page-1')); - final keptRow = server.row(kept); - - container.read(lineUpProvider.notifier).deleteLink(deleted); - await _settle(); - // Only the deletion is written. - final ops = lineupOps(queuedOps()); - expect(ops.keys, [keyOf(page, deleted)]); - expect(ops.values.single, isA()); - expect(drawnLinks(container).single.$1, kept); - expect( - landingAt(container, drawnLinks(container).single.$3), - keptAt, - ); - - await land(container, remote, page, contentRevision: 2); - expect(server.row(deleted).deleted, isTrue); - expect(server.received, hasLength(1)); - // The kept lineup's row is as it was, its own copy and all. - expect(server.row(kept).revision, keptRow.revision); - expect( - canonicalCloudJsonEncode(server.row(kept).payload), - canonicalCloudJsonEncode(keptRow.payload), - ); - // Drawn on its copy, under the landing's own id now nothing - // disagrees with it, here and for anyone opening the page. - expect(drawnLinks(container).single.$3, 'landing'); - expect(landingAt(container, 'landing'), keptAt); - expect( - lineUpGraphFromRemoteLineups(server.liveRows) - .landings - .single - .ability - .position, - keptAt, - ); - expect(lineupOps(queuedOps()), isEmpty); - expect(desiredOps(container, page), isEmpty); - await _settle(); - }); - } - - test( - 'moving one copy of a split spot patches only the rows carrying it, ' - 'and once they agree the spot is drawn as one', () async { - final (container, remote, page) = await openEmpty([ - ...divergedFanIn('page-1'), - _lineupRow('page-1', 'link-c', - origin: _originJson('origin-c', const Offset(90, 20)), - landing: _landingJson('landing', older), - revision: 4, - sortIndex: 2), - ]); - const moved = Offset(500, 500); - - /// The landing [op] writes: under the real id, its marker naming it, - /// at [moved]. - void expectMovedLanding(StrategyOp op) { - final landing = - cloudPayloadData((op as LineupPatchOp).payload)['landing'] as Map; - expect(landing['id'], 'landing'); - expect((landing['ability'] as Map)['lineUpID'], 'landing'); - expect((landing['ability'] as Map)['position'], - {'dx': moved.dx, 'dy': moved.dy}); - } - - // The aliased copy, B's and C's: only their rows, each against its - // own revision. - container - .read(lineUpProvider.notifier) - .updateLandingPosition('landing@link-b', moved); - var ops = lineupOps(desiredOps(container, page)); - expect( - { - for (final op in ops.values.cast()) - op.lineupPublicId: op.expectedLineupRevision, - }, - {'link-b': 7, 'link-c': 4}, - ); - ops.values.forEach(expectMovedLanding); - await land(container, remote, page, contentRevision: 2); - expect( - {for (final row in server.liveRows) row.publicId: row.revision}, - {'link-a': 1, 'link-b': 8, 'link-c': 5}, - ); - // A's copy still differs, so the spot is still drawn split. - expect(landingAt(container, 'landing'), newer); - expect(landingAt(container, 'landing@link-b'), moved); - - // The real-id copy, A's: only A's row. - container - .read(lineUpProvider.notifier) - .updateLandingPosition('landing', moved); - ops = lineupOps(desiredOps(container, page)); - expect(ops.keys, [keyOf(page, 'link-a')]); - expect((ops.values.single as LineupPatchOp).expectedLineupRevision, 1); - expectMovedLanding(ops.values.single); - await land(container, remote, page, contentRevision: 3); - expect( - {for (final row in server.liveRows) row.publicId: row.revision}, - {'link-a': 2, 'link-b': 8, 'link-c': 5}, - ); - - // Every copy agrees: one spot again, every lineup on it. - expect(container.read(lineUpProvider).landings.map((l) => l.id), - ['landing']); - expect(landingAt(container, 'landing'), moved); - expect( - drawnLinks(container).map((link) => link.$3).toSet(), - {'landing'}, - ); - expect(server.received, hasLength(3)); - expect(lineupOps(queuedOps()), isEmpty); - expect(desiredOps(container, page), isEmpty); - await _settle(); - }); + /// Shows the page as [server] now holds it, as the live read would. + late void Function() showServer; /// Opens [page] on the real queue, its outbox kept in [store], which /// sends to [server] through [repository] and reads it back after every - /// batch. A batch waits for [hold] while it is set. Returns the - /// container and the batches sent. + /// batch. A batch waits for [hold] while it is set. The queue reaches + /// the server while [_online] holds. Returns the container and the + /// batches sent. Future<(ProviderContainer, List>)> openOnRealQueue( RemotePage page, { DurableStrategyOutboxStore? store, @@ -6438,7 +6407,8 @@ void main() { lineups: server.rows, ), ); - final remote = _FakeRemoteEditorNotifier(read()); + final remote = liveRead = _FakeRemoteEditorNotifier(read()); + showServer = () => remote.setSnapshot(read()); repository = _ServerRepository( server, afterBatch: () => remote.initialSnapshot = read(), @@ -6449,7 +6419,8 @@ void main() { .overrideWithValue(store ?? MemoryDurableStrategyOutboxStore()), convexStrategyRepositoryProvider.overrideWithValue(repository), authProvider.overrideWith(_CloudReadyAuthProvider.new), - convexConnectionSnapshotProvider.overrideWithValue(true), + convexConnectionSnapshotProvider + .overrideWith((ref) => ref.watch(_online)), cloudMediaAccountIdProvider.overrideWithValue('account-a'), cloudMediaUploadQueueProvider .overrideWith(() => _RecordingMediaQueue()), @@ -6566,11 +6537,11 @@ void main() { // Keep mine brought it back over the tombstone, as the user has it. expect(restore, isA()); expect((restore as LineupAddOp).expectedLineupRevision, 2); - expect(cloudPayloadData(restore.payload)['notes'], 'mine'); + expect(_onlyLinkIn(restore.payload)['notes'], 'mine'); final row = server.row('link-a'); expect(row.deleted, isFalse); expect(row.revision, 3); - expect(cloudPayloadData(row.payload)['notes'], 'mine'); + expect(_onlyLinkIn(row.payload)['notes'], 'mine'); expect(lineUpGraphFromRemoteLineups(server.liveRows).links.single.notes, 'mine'); expect(onScreen()?.notes, 'mine'); @@ -6720,29 +6691,33 @@ void main() { }); test( - 'a recovered edit to a lineup on a split spot is sent as it was, and ' - 'opening the page writes nothing else', () async { + 'a recovered edit to a lineup group is sent as it was, and opening ' + 'the page writes nothing else', () async { final page = _page('page-1', 0); - server = - _FakeServer(page.publicId, lineups: divergedFanIn(page.publicId)); - final rowA = server.row('link-a'); - final rowB = server.row('link-b'); - final bKey = keyOf(page, 'link-b'); + server = _FakeServer(page.publicId, lineups: [ + fanIn(page.publicId), + _lineup(page.publicId, 'd', sortIndex: 1), + ]); + final group = server.row('link-a'); + final other = server.row('link-d'); + final key = keyOf(page, 'link-a'); // Before a restart, the user's notes edit to B was saved to the outbox // but never sent. final recovered = LineupPatchOp( opId: 'recovered-notes', - lineupPublicId: 'link-b', + lineupPublicId: 'link-a', pagePublicId: page.publicId, - payload: - _editedLineup(rowB, (data) => data['notes'] = 'recovered').payload, - expectedLineupRevision: rowB.revision, + payload: _editedGroup( + group, + (data) => _entry(data, 'links', 'link-b')['notes'] = 'recovered', + ).payload, + expectedLineupRevision: group.revision, ); final store = MemoryDurableStrategyOutboxStore(); await store.put(DurableOutboxRecord( accountId: 'account-a', strategyPublicId: 'cloud-strategy', - entityKey: bKey, + entityKey: key, pending: PendingOp(op: recovered, clientId: 'client-before-restart'), status: DurableOutboxStatus.queued, createdAt: DateTime.utc(2026), @@ -6761,10 +6736,10 @@ void main() { bool isLineup(StrategyOp op) => EntitySyncKey.forStrategyOp(op)?.kind == EntitySyncKeyKind.lineup; - // The page draws B on its own copy of the landing, and the recovered - // edit is the only lineup work: nothing replaced it, nothing joined it. + // The page draws both groups, and the recovered edit is the only + // lineup work: nothing replaced it, nothing joined it. expect(container.read(lineUpProvider).landings.map((l) => l.id), - ['landing', 'landing@link-b']); + ['landing', 'landing-d']); expect( [ for (final pending in queueState().pending) @@ -6793,20 +6768,22 @@ void main() { ['recovered-notes'], ); expect(server.received.single.payload, recovered.payload); - expect(server.row('link-b').revision, rowB.revision + 1); + expect(server.row('link-a').revision, group.revision + 1); + expect(_linkIn(server.row('link-a').payload, 'link-b')['notes'], + 'recovered'); + expect(server.row('link-d').revision, other.revision); expect( - cloudPayloadData(server.row('link-b').payload)['notes'], 'recovered'); - expect(server.row('link-a').revision, rowA.revision); - expect( - canonicalCloudJsonEncode(server.row('link-a').payload), - canonicalCloudJsonEncode(rowA.payload), + canonicalCloudJsonEncode(server.row('link-d').payload), + canonicalCloudJsonEncode(other.payload), ); expect( container.read(lineUpProvider).linkById('link-b')!.notes, 'recovered', ); - expect(landingAt(container, 'landing@link-b'), older); - expect(landingAt(container, 'landing'), newer); + expect( + container.read(lineUpProvider).landingById('landing')!.ability.position, + newer, + ); }); test( @@ -6826,8 +6803,8 @@ void main() { // The user's notes edit reaches the server, which takes it at // revision 2, but the answer is lost; a teammate renames the lineup // right after. - repository.loseNextAnswer = () => - server.teammateEdit('link-a', (data) => data['name'] = 'theirs'); + repository.loseNextAnswer = () => server.teammateEdit( + 'link-a', _eachLink((link) => link['name'] = 'theirs')); container .read(lineUpProvider.notifier) .updateLink(onScreen().copyWith(notes: 'one')); @@ -6849,13 +6826,578 @@ void main() { final sent = sentFor(batches, key); expect(sent, hasLength(3)); expect(sent[1].opId, sent[0].opId); - expect(cloudPayloadData(sent[2].payload)['notes'], 'two'); + expect(_onlyLinkIn((sent[2] as LineupPatchOp).payload)['notes'], 'two'); expect((sent[2] as LineupPatchOp).expectedLineupRevision, 2); final row = server.row('link-a'); expect(row.revision, 3); - expect(cloudPayloadData(row.payload)['name'], 'theirs'); - expect(cloudPayloadData(row.payload)['notes'], 'one'); + expect(_onlyLinkIn(row.payload)['name'], 'theirs'); + expect(_onlyLinkIn(row.payload)['notes'], 'one'); + expect(container.read(strategyConflictProvider), hasLength(1)); + }); + + /// Every id in [graph]: its lineups, spots, and the agent and ability on + /// each spot. + Set idsIn(LineUpGraph graph) => { + for (final origin in graph.origins) ...[origin.id, origin.agent.id], + for (final landing in graph.landings) ...[ + landing.id, + landing.ability.id, + ], + for (final link in graph.links) link.id, + }; + + /// The lineups [rows] draw, each as `name: notes`. + List lineupsIn(Iterable rows) => [ + for (final link in lineUpGraphFromRemoteLineups(rows).links) + '${link.name}: ${link.notes}', + ]; + + /// The changes [changes] lists, each as `label: description`. + List described(List changes) => [ + for (final change in changes) + '${change.label}: ${change.description}', + ]; + + /// The fan-in group is on the server and on screen; a teammate's notes + /// edit to B lands, then this client's notes edit to A, made against the + /// same revision, is refused and waits. Returns the container, the + /// batches sent and the outbox. + Future< + ( + ProviderContainer, + List>, + MemoryDurableStrategyOutboxStore, + )> refusedBesideTeammate(RemotePage page) async { + server = _FakeServer(page.publicId, lineups: [fanIn(page.publicId)]); + final store = MemoryDurableStrategyOutboxStore(); + final (container, batches) = await openOnRealQueue(page, store: store); + final key = keyOf(page, 'link-a'); + + // Both clients start from revision 7; the teammate's save lands first. + server.teammateEdit('link-a', + (data) => _entry(data, 'links', 'link-b')['notes'] = 'theirs'); + container.read(lineUpProvider.notifier).updateLink(container + .read(lineUpProvider) + .linkById('link-a')! + .copyWith(notes: 'mine')); + await _until(() => container + .read(strategyOpQueueProvider) + .attentionByEntityKey + .containsKey(key)); + for (var i = 0; i < 10; i++) { + await _settle(); + } + return (container, batches, store); + } + + test( + 'two clients changing different lineups of one group from the same ' + 'revision: the second is refused and waits, nothing overwritten', + () async { + final page = _page('page-1', 0); + final (container, batches, store) = await refusedBesideTeammate(page); + final key = keyOf(page, 'link-a'); + final queueState = container.read(strategyOpQueueProvider); + + final sent = sentFor(batches, key); + expect(sent.single, isA()); + expect((sent.single as LineupPatchOp).expectedLineupRevision, 7); + expect( + queueState.lastAckBatch + .singleWhere((acked) => acked.entityKey == key) + .ack, + isA().having((ack) => ack.rejectionReason, 'reason', + OpRejectionReason.revisionMismatch), + ); + + // The server keeps the teammate's version; the user's waits, on + // screen and in the outbox, for them to choose. + expect(server.row('link-a').revision, 8); + expect( + lineupsIn(server.liveRows), ['Heaven: remote lineup', 'Mid: theirs']); + expect(container.read(lineUpProvider).linkById('link-a')!.notes, 'mine'); + expect(queueState.attentionByEntityKey.keys, [key]); + expect(store.load().records.single.status, DurableOutboxStatus.attention); + expect(container.read(strategyConflictProvider), hasLength(1)); + + // The sync popover lists what each side changed. + final conflict = container.read(lineupConflictsProvider)!.single; + expect(conflict.key, key); + expect(described(conflict.yours), ['Heaven: notes edited']); + expect(described(conflict.cloud), ['Mid: notes edited']); + }); + + test( + 'offline edits to a group a teammate changed meanwhile wait in ' + 'attention on reconnect', () async { + final page = _page('page-1', 0); + server = _FakeServer(page.publicId, lineups: [fanIn(page.publicId)]); + final store = MemoryDurableStrategyOutboxStore(); + final (container, batches) = await openOnRealQueue(page, store: store); + final key = keyOf(page, 'link-a'); + StrategyOpQueueState queueState() => + container.read(strategyOpQueueProvider); + const moved = Offset(400, 400); + + container.read(_online.notifier).state = false; + final lineUps = container.read(lineUpProvider.notifier); + lineUps.updateLink(container + .read(lineUpProvider) + .linkById('link-a')! + .copyWith(notes: 'offline')); + lineUps.updateOriginAgentPosition('origin-a', moved); + for (var i = 0; i < 10; i++) { + await _settle(); + } + expect(sentFor(batches, key), isEmpty); + expect(queueState().queuedByEntityKey.keys, contains(key)); + + // Meanwhile a teammate renames B. + server.teammateEdit( + 'link-a', (data) => _entry(data, 'links', 'link-b')['name'] = 'B'); + + container.read(_online.notifier).state = true; + await container.read(strategyOpQueueProvider.notifier).flushNow(); + await _until(() => queueState().attentionByEntityKey.containsKey(key)); + for (var i = 0; i < 10; i++) { + await _settle(); + } + + // Everything made offline went as one write, against the revision the + // user saw, and was refused rather than stored over the rename. + final sent = sentFor(batches, key).single as LineupPatchOp; + expect(sent.expectedLineupRevision, 7); + expect(_linkIn(sent.payload, 'link-a')['notes'], 'offline'); + expect(_linkIn(sent.payload, 'link-b')['name'], 'Mid'); + expect(server.row('link-a').revision, 8); + expect(lineupsIn(server.liveRows), + ['Heaven: remote lineup', 'B: remote lineup']); + expect( + ((_entry(cloudPayloadData(server.row('link-a').payload), 'origins', + 'origin-a')['agent'] as Map)['position'] as Map)['dx'], + 10, + ); + final onScreen = container.read(lineUpProvider); + expect(onScreen.linkById('link-a')!.notes, 'offline'); + expect(onScreen.originById('origin-a')!.agent.position, moved); + expect(store.load().records.single.status, DurableOutboxStatus.attention); + expect(container.read(strategyConflictProvider), hasLength(1)); + }); + + test('offline edits to a group nobody changed land on reconnect', () async { + final page = _page('page-1', 0); + server = _FakeServer(page.publicId, lineups: [fanIn(page.publicId)]); + final store = MemoryDurableStrategyOutboxStore(); + final (container, batches) = await openOnRealQueue(page, store: store); + final key = keyOf(page, 'link-a'); + StrategyOpQueueState queueState() => + container.read(strategyOpQueueProvider); + const moved = Offset(400, 400); + + container.read(_online.notifier).state = false; + final lineUps = container.read(lineUpProvider.notifier); + lineUps.updateLink(container + .read(lineUpProvider) + .linkById('link-a')! + .copyWith(notes: 'offline')); + lineUps.updateOriginAgentPosition('origin-a', moved); + for (var i = 0; i < 10; i++) { + await _settle(); + } + expect(sentFor(batches, key), isEmpty); + + container.read(_online.notifier).state = true; + await container.read(strategyOpQueueProvider.notifier).flushNow(); + await _until(() => + queueState().pending.isEmpty && sentFor(batches, key).isNotEmpty); + for (var i = 0; i < 10; i++) { + await _settle(); + } + + expect(sentFor(batches, key).single, isA()); + final row = server.row('link-a'); + expect(row.revision, 8); + expect(lineupsIn([row]), ['Heaven: offline', 'Mid: remote lineup']); + expect( + ((_entry(cloudPayloadData(row.payload), 'origins', 'origin-a')['agent'] + as Map)['position'] as Map)['dx'], + moved.dx, + ); + expect(queueState().attentionByEntityKey, isEmpty); + expect(queueState().needsAttention, isFalse); + expect(store.load().records, isEmpty); + expect(container.read(strategyConflictProvider), isEmpty); + }); + + test( + 'an edit behind one replayed with no revision waits in attention ' + 'instead of going out on a guessed revision', () async { + final page = _page('page-1', 0); + // The server takes the first edit but records no revision for it, as + // events written before it kept one. + server = + _FakeServer(page.publicId, lineups: [_lineup(page.publicId, 'a')]) + ..recordsRevisions = false; + final hold = Completer(); + final store = MemoryDurableStrategyOutboxStore(); + final (container, batches) = + await openOnRealQueue(page, store: store, hold: hold); + final key = _lineupKey(page.publicId, 'a'); + StrategyOpQueueState queueState() => + container.read(strategyOpQueueProvider); + LineUpLink onScreen() => + container.read(lineUpProvider).linkById('link-a')!; + + // The first edit lands at revision 2, but its answer is lost; a + // teammate renames the lineup right after. + repository.loseNextAnswer = () => server.teammateEdit( + 'link-a', _eachLink((link) => link['name'] = 'theirs')); + container + .read(lineUpProvider.notifier) + .updateLink(onScreen().copyWith(notes: 'one')); + await _until(() => queueState().inFlightByEntityKey.containsKey(key)); + // The user edits again; that waits behind it. + container + .read(lineUpProvider.notifier) + .updateLink(onScreen().copyWith(notes: 'two')); + await _until(() => queueState().successorByEntityKey.containsKey(key)); + hold.complete(); + + await _until(() => queueState().attentionByEntityKey.containsKey(key)); + for (var i = 0; i < 10; i++) { + await _settle(); + } + + // The replay is answered as a noop with no revision... + final sent = sentFor(batches, key); + expect(sent, hasLength(2)); + expect(sent[1].opId, sent[0].opId); + final replayed = queueState() + .lastAckBatch + .where((acked) => acked.entityKey == key) + .map((acked) => acked.ack); + expect( + replayed, + everyElement(isA() + .having((ack) => ack.currentRevision, 'revision', isNull)), + ); + // ...so the edit behind it is never sent on a revision the client + // would have had to guess, which would store it over the teammate's + // rename. It waits for the user to choose. + expect( + _onlyLinkIn(queueState().successorByEntityKey[key]!.pending.op.payload + as CloudPayload)['notes'], + 'two', + ); + expect( + store.load().records.singleWhere((r) => r.entityKey == key).status, + DurableOutboxStatus.attention, + ); + final row = server.row('link-a'); + expect(row.revision, 3); + expect(_onlyLinkIn(row.payload)['name'], 'theirs'); + expect(_onlyLinkIn(row.payload)['notes'], 'one'); + expect(onScreen().notes, 'two'); + }); + + /// The fan-in group is on the server and on screen. Offline, the user + /// edits A's notes while a teammate deletes the group; on reconnect the + /// edit is refused as deleted and waits. Returns the container and the + /// batches sent. + Future<(ProviderContainer, List>)> deletedWhileOffline( + RemotePage page, + ) async { + server = _FakeServer(page.publicId, lineups: [fanIn(page.publicId)]); + final (container, batches) = await openOnRealQueue(page); + container.read(_online.notifier).state = false; + container.read(lineUpProvider.notifier).updateLink(container + .read(lineUpProvider) + .linkById('link-a')! + .copyWith(notes: 'mine')); + for (var i = 0; i < 10; i++) { + await _settle(); + } + server.teammateDelete('link-a'); + expect(server.row('link-a').revision, 8); + + container.read(_online.notifier).state = true; + await container.read(strategyOpQueueProvider.notifier).flushNow(); + await refusedAsDeleted(container, batches, keyOf(page, 'link-a')); + // The user's version is still on screen. + expect(container.read(lineUpProvider).links.map((link) => link.notes), + ['mine', 'remote lineup']); + return (container, batches); + } + + test( + 'Keep mine brings back a group a teammate deleted while the user ' + 'edited it offline, as the user has it', () async { + final page = _page('page-1', 0); + final (container, batches) = await deletedWhileOffline(page); + + final restore = + await refusedThenKeptMine(container, batches, keyOf(page, 'link-a')); + + expect(restore, isA()); + expect((restore as LineupAddOp).expectedLineupRevision, 8); + final row = server.row('link-a'); + expect(row.deleted, isFalse); + expect(row.revision, 9); + expect(_groupsOf([row]), { + 'link-a': { + 'origins': ['origin-a', 'origin-b'], + 'landings': ['landing'], + 'links': ['link-a', 'link-b'], + }, + }); + expect(lineupsIn([row]), ['Heaven: mine', 'Mid: remote lineup']); + expect(container.read(lineUpProvider).links.map((link) => link.notes), + ['mine', 'remote lineup']); + }); + + test( + 'Keep both after a teammate deleted the group leaves it deleted and ' + 'adds the user\'s version as a copy', () async { + final page = _page('page-1', 0); + final (container, batches) = await deletedWhileOffline(page); + StrategyOpQueueState queueState() => + container.read(strategyOpQueueProvider); + final original = idsIn(container.read(lineUpProvider).graph); + expect(container.read(lineupConflictsProvider), isNotNull); + + final kept = await container + .read(strategyPageSessionProvider.notifier) + .keepBothForRejected(); + expect(kept, KeepBothOutcome.kept); + await _until(() => queueState().pending.isEmpty); + for (var i = 0; i < 10; i++) { + await _settle(); + } + + // The group stays deleted, as the teammate left it, and was never + // written again. + expect(server.row('link-a').deleted, isTrue); + expect(server.row('link-a').revision, 8); + expect(sentFor(batches, keyOf(page, 'link-a')), hasLength(1)); + // The user's version is beside it as a group of its own, nothing in it + // the original. + final copy = server.liveRows.single; + final copyGraph = lineUpGraphFromRemoteLineups([copy]); + expect(idsIn(copyGraph).intersection(original), isEmpty); + expect(lineupsIn([copy]), ['Heaven: mine', 'Mid: remote lineup']); + expect(copyGraph.landings, hasLength(1)); + expect(copyGraph.origins, hasLength(2)); + expect(sentFor(batches, keyOf(page, copy.publicId)).single, + isA()); + // On screen, the copy alone; nothing waits. + expect(idsIn(container.read(lineUpProvider).graph), idsIn(copyGraph)); + expect(queueState().attentionByEntityKey, isEmpty); + expect(queueState().needsAttention, isFalse); + expect(container.read(strategyConflictProvider), isEmpty); + expect(container.read(lineupConflictsProvider), isNull); + }); + + test( + 'Keep both keeps the cloud\'s group as the server has it and queues ' + 'the user\'s version as a new group under fresh ids', () async { + final page = _page('page-1', 0); + final (container, batches, store) = await refusedBesideTeammate(page); + StrategyOpQueueState queueState() => + container.read(strategyOpQueueProvider); + final original = idsIn(container.read(lineUpProvider).graph); + + final kept = await container + .read(strategyPageSessionProvider.notifier) + .keepBothForRejected(); + expect(kept, KeepBothOutcome.kept); + await _until(() => queueState().pending.isEmpty); + for (var i = 0; i < 10; i++) { + await _settle(); + } + + // The cloud's group is untouched: the teammate's version, revision 8. + final group = server.row('link-a'); + expect(group.revision, 8); + expect(lineupsIn([group]), ['Heaven: remote lineup', 'Mid: theirs']); + expect(sentFor(batches, keyOf(page, 'link-a')), hasLength(1)); + // The user's version went to the server as a new group row. + final copy = + server.liveRows.singleWhere((row) => row.publicId != 'link-a'); + final copyGraph = lineUpGraphFromRemoteLineups([copy]); + expect(idsIn(copyGraph).intersection(original), isEmpty); + expect(lineupsIn([copy]), ['Heaven: mine', 'Mid: remote lineup']); + expect(sentFor(batches, keyOf(page, copy.publicId)).single, + isA()); + // Both are on screen, and nothing waits. + final onScreen = container.read(lineUpProvider); + expect( + [for (final link in onScreen.links) '${link.name}: ${link.notes}'], + unorderedEquals([ + 'Heaven: remote lineup', + 'Mid: theirs', + 'Heaven: mine', + 'Mid: remote lineup', + ])); + expect(queueState().attentionByEntityKey, isEmpty); + expect(queueState().needsAttention, isFalse); + expect(store.load().records, isEmpty); + expect(container.read(strategyConflictProvider), isEmpty); + expect(container.read(lineupConflictsProvider), isNull); + }); + + test( + 'Keep both whose cloud version cannot be loaded changes nothing, so ' + 'pressing it again cannot copy twice', () async { + final page = _page('page-1', 0); + final (container, batches, store) = await refusedBesideTeammate(page); + final key = keyOf(page, 'link-a'); + StrategyOpQueueState queueState() => + container.read(strategyOpQueueProvider); + final before = [ + for (final link in container.read(lineUpProvider).links) link.id, + ]; + liveRead.readFails = true; + + final outcome = await container + .read(strategyPageSessionProvider.notifier) + .keepBothForRejected(); + expect(outcome, KeepBothOutcome.unchanged); + for (var i = 0; i < 10; i++) { + await _settle(); + } + + // No copy, on screen or on the server... + expect( + [for (final link in container.read(lineUpProvider).links) link.id], + before, + ); + expect(server.liveRows, hasLength(1)); + // ...and the original work still waits for the user to choose. + expect(queueState().attentionByEntityKey.keys, [key]); + expect( + store.load().records.singleWhere((r) => r.entityKey == key).status, + DurableOutboxStatus.attention, + ); + expect(sentFor(batches, key), hasLength(1)); + expect(container.read(strategyConflictProvider), hasLength(1)); + }); + + test( + 'after Keep both, exporting the strategy to .ica and importing it ' + 'brings back both versions', () async { + final page = _page('page-1', 0); + final (container, _, _) = await refusedBesideTeammate(page); + final kept = await container + .read(strategyPageSessionProvider.notifier) + .keepBothForRejected(); + expect(kept, KeepBothOutcome.kept); + await _until(() => + container.read(strategyOpQueueProvider).pending.isEmpty && + server.liveRows.length == 2); + + // Exported from the server's copy, as the cloud library exports. + final now = DateTime.utc(2026); + final exported = + StrategyImportExportService.strategyDataFromRemoteSnapshot( + RemoteFullStrategySnapshot( + header: RemoteStrategyHeader( + publicId: 'cloud-strategy', + name: 'Cloud Strategy', + mapData: Maps.mapNames[MapValue.ascent]!, + revision: 1, + createdAt: now, + updatedAt: now, + ), + pages: [ + RemoteFullPage( + page: page, + content: RemotePageContent( + settings: settingsFor(1), + revision: 1, + createdAt: now, + updatedAt: now, + ), + ), + ], + elementsByPage: const {}, + lineupsByPage: {page.publicId: server.rows}, + assetsById: const {}, + ), + ); + + final box = await _openStrategyBox(); + await Hive.openBox(HiveBoxNames.mapThemeProfilesBox); + final files = await Directory.systemTemp.createTemp('icarus-keep-both-'); + addTearDown(() => files.delete(recursive: true)); + final messenger = + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger; + const pathProvider = MethodChannel('plugins.flutter.io/path_provider'); + messenger.setMockMethodCallHandler( + pathProvider, (call) async => files.path); + addTearDown(() => messenger.setMockMethodCallHandler(pathProvider, null)); + + final service = StrategyImportExportService(container); + final archive = await service.zipStrategyData( + strategy: exported, + outputFilePath: '${files.path}${Platform.pathSeparator}Keep both.ica', + ); + await service.loadFromFilePath(archive); + + final imported = box.values.single; + final graph = imported.pages.single.lineUpGraph; + List described(LineUpGraph graph) => [ + for (final link in graph.links) '${link.name}: ${link.notes}', + ]; + expect( + described(graph), + described(exported.pages.single.lineUpGraph), + ); + expect(described(graph), [ + 'Heaven: remote lineup', + 'Mid: theirs', + 'Heaven: mine', + 'Mid: remote lineup', + ]); + expect(graph.origins, hasLength(4)); + expect(graph.landings, hasLength(2)); + expect( + jsonEncode(graph.toJson()), + jsonEncode(exported.pages.single.lineUpGraph.toJson()), + ); + }); + + test( + 'refused work the user then deletes on canvas stays in attention ' + 'unless the refusal said the item was deleted', () async { + final page = _page('page-1', 0); + final (container, batches, store) = await refusedBesideTeammate(page); + final key = keyOf(page, 'link-a'); + StrategyOpQueueState queueState() => + container.read(strategyOpQueueProvider); + + // The teammate then deletes the group, and the user deletes both its + // lineups on the canvas: both sides have it gone, but the refusal was + // a revision mismatch, not a deletion. + server.teammateDelete('link-a'); + showServer(); + final lineUps = container.read(lineUpProvider.notifier); + lineUps.deleteLink('link-a'); + lineUps.deleteLink('link-b'); + expect(container.read(lineUpProvider).links, isEmpty); + for (var i = 0; i < 20; i++) { + await _settle(); + } + + // Nothing about the refusal says the server deleted what it edits, so + // the work is not dropped in silence: it still waits. + expect(queueState().attentionByEntityKey.keys, [key]); + expect(queueState().needsAttention, isTrue); + expect( + store.load().records.singleWhere((r) => r.entityKey == key).status, + DurableOutboxStatus.attention, + ); expect(container.read(strategyConflictProvider), hasLength(1)); + expect(sentFor(batches, key), hasLength(1)); }); }); @@ -7995,7 +8537,7 @@ void main() { /// [lineup]'s row as a teammate left it, with [notes]. List withNotes(RemoteLineup lineup, String notes) => - [_editedLineup(lineup, (data) => data['notes'] = notes)]; + [_editedLineup(lineup, (link) => link['notes'] = notes)]; test( 'redoing a lineup clear a teammate already undid by deleting ' diff --git a/test/widgets/cloud_sync_button_test.dart b/test/widgets/cloud_sync_button_test.dart index 3e970547..028e5c8a 100644 --- a/test/widgets/cloud_sync_button_test.dart +++ b/test/widgets/cloud_sync_button_test.dart @@ -13,6 +13,7 @@ import 'package:icarus/providers/collab/client_upgrade_required_provider.dart'; import 'package:icarus/providers/collab/cloud_media_upload_queue_provider.dart'; import 'package:icarus/providers/collab/cloud_sync_status_provider.dart'; import 'package:icarus/providers/collab/convex_connection_provider.dart'; +import 'package:icarus/providers/collab/lineup_conflicts_provider.dart'; import 'package:icarus/providers/collab/strategy_conflict_provider.dart'; import 'package:icarus/providers/collab/strategy_op_queue_provider.dart'; import 'package:icarus/providers/desktop_update_provider.dart'; @@ -21,6 +22,7 @@ import 'package:icarus/providers/strategy_provider.dart'; import 'package:icarus/providers/strategy_save_state_provider.dart'; import 'package:icarus/providers/text_draft_provider.dart'; import 'package:icarus/providers/update_status_provider.dart'; +import 'package:icarus/strategy/lineup_group_changes.dart'; import 'package:icarus/strategy/strategy_page_models.dart'; import 'package:icarus/widgets/client_upgrade_button.dart'; import 'package:icarus/widgets/cloud_sync_button.dart'; @@ -188,11 +190,15 @@ class _AttentionOpQueue extends StrategyOpQueueNotifier { } class _ConflictSession extends StrategyPageSessionNotifier { - _ConflictSession({this.result = true, this.failure}); + _ConflictSession({this.result = true, this.failure, this.keepBoth}); final bool result; final Object? failure; + + /// What Keep both answers; by default what [result] says. + final KeepBothOutcome? keepBoth; int useCloudCount = 0; + int keepBothCount = 0; @override StrategyPageSessionState build() => const StrategyPageSessionState( @@ -208,6 +214,14 @@ class _ConflictSession extends StrategyPageSessionNotifier { if (failure != null) throw failure!; return result; } + + @override + Future keepBothForRejected() async { + keepBothCount += 1; + if (failure != null) throw failure!; + return keepBoth ?? + (result ? KeepBothOutcome.kept : KeepBothOutcome.unchanged); + } } class _DeletedPageSession extends StrategyPageSessionNotifier { @@ -295,12 +309,14 @@ ProviderContainer _createContainer({ ProviderContainer _createConflictContainer({ required _AttentionOpQueue queue, required _ConflictSession session, + List? lineupConflicts, }) { return ProviderContainer( overrides: [ strategyProvider.overrideWith(_CloudStrategyProvider.new), strategyOpQueueProvider.overrideWith(() => queue), strategyPageSessionProvider.overrideWith(() => session), + lineupConflictsProvider.overrideWithValue(lineupConflicts), cloudMediaUploadQueueProvider.overrideWith(_EmptyMediaQueue.new), cloudMediaAccountIdProvider.overrideWithValue('account-a'), convexConnectionProvider.overrideWith((ref) => Stream.value(true)), @@ -911,6 +927,10 @@ void main() { find.textContaining('applies to all 2 conflicting changes'), findsOneWidget, ); + // An element conflict has no lineup changes to list and nothing to keep + // beside the cloud's version. + expect(find.text('Keep both'), findsNothing); + expect(find.text('Your changes'), findsNothing); await tester.tap(find.text('Use cloud')); await tester.pumpAndSettle(); @@ -920,6 +940,133 @@ void main() { expect(queue.flushNowCount, 0); }); + group('lineup conflicts', () { + LineupChange change(String label, String description) => + LineupChange(label: label, description: description); + + final conflicts = [ + LineupGroupConflict( + key: const EntitySyncKey.lineup('page-1', 'link-a'), + yours: [change('Heaven', 'notes edited')], + cloud: [ + change('Mid', 'renamed'), + change('Short', 'added'), + change('Long', 'landing moved'), + change('Deep', 'deleted'), + change('Sova lineup', 'images changed'), + ], + ), + ]; + + Future openPopover( + WidgetTester tester, ProviderContainer container) async { + // The test font sets every glyph a full em wide, so the lists wrap far + // more than in the app; a desktop-sized window gives them the room. + tester.view.physicalSize = const Size(1280, 1024); + tester.view.devicePixelRatio = 1; + addTearDown(tester.view.reset); + await tester.pumpWidget( + UncontrolledProviderScope( + container: container, + child: const ShadApp( + home: Scaffold( + body: CloudSyncButton(style: kEditorToolbarButtonStyle)), + ), + ), + ); + await tester.pump(); + await tester.tap(_syncButton('attention')); + await tester.pumpAndSettle(); + } + + Finder line(String text) => find.text(text, findRichText: true); + + testWidgets('list what each side changed and offer Keep both', + (tester) async { + final queue = _AttentionOpQueue(1); + final session = _ConflictSession(); + final container = _createConflictContainer( + queue: queue, + session: session, + lineupConflicts: conflicts, + ); + addTearDown(container.dispose); + await openPopover(tester, container); + + expect( + find.textContaining('A teammate changed these lineups'), + findsOneWidget, + ); + expect(find.text('Your changes'), findsOneWidget); + expect(line('Heaven · notes edited'), findsOneWidget); + expect(find.text('Cloud changes'), findsOneWidget); + expect(line('Mid · renamed'), findsOneWidget); + expect(line('Short · added'), findsOneWidget); + expect(line('Long · landing moved'), findsOneWidget); + expect(line('Deep · deleted'), findsOneWidget); + // Past four lines the rest are counted. + expect(line('Sova lineup · images changed'), findsNothing); + expect(find.text('and 1 more lineup'), findsOneWidget); + expect(find.text('Use cloud'), findsOneWidget); + expect(find.text('Keep mine'), findsOneWidget); + expect(find.text('Keep both'), findsOneWidget); + + await tester.tap(find.text('Keep both')); + await tester.pumpAndSettle(); + + expect(session.keepBothCount, 1); + expect(session.useCloudCount, 0); + expect(queue.retryRejectedCount, 0); + expect(queue.flushNowCount, 0); + }); + + testWidgets('a Keep both that cannot load the cloud version says so', + (tester) async { + final queue = _AttentionOpQueue(1); + final session = _ConflictSession(result: false); + final container = _createConflictContainer( + queue: queue, + session: session, + lineupConflicts: conflicts, + ); + addTearDown(container.dispose); + await openPopover(tester, container); + + await tester.tap(find.text('Keep both')); + await tester.pumpAndSettle(); + + expect(session.keepBothCount, 1); + expect( + find.text('Could not load the cloud version. Nothing was changed.'), + findsOneWidget, + ); + expect(find.text('Keep both'), findsOneWidget); + }); + + testWidgets( + 'a Keep both that copied but then lost the cloud version points to ' + 'Use cloud', (tester) async { + final queue = _AttentionOpQueue(1); + final session = _ConflictSession(keepBoth: KeepBothOutcome.copiesOnly); + final container = _createConflictContainer( + queue: queue, + session: session, + lineupConflicts: conflicts, + ); + addTearDown(container.dispose); + await openPopover(tester, container); + + await tester.tap(find.text('Keep both')); + await tester.pumpAndSettle(); + + expect( + find.textContaining('Your version was added as a copy'), + findsOneWidget, + ); + expect(find.textContaining('Choose Use cloud to finish'), findsOneWidget); + }); + }); + testWidgets( 'active conflict controls remain when another strategy needs attention', (tester) async { From 9724df44e40fe1a38d4fc820d185144f208a606b Mon Sep 17 00:00:00 2001 From: Dara Adedeji Date: Fri, 2 Oct 2026 18:36:21 -0400 Subject: [PATCH 08/14] Never merge lineup groups; make Keep both safe to interrupt From review of the group rows: - Groups no longer merge. A lineup stays in the group it was written to, new lineups join the group of a spot they share, and a row goes only when its last lineup does. A merge deleted the other group's row, and a teammate's edit to it could be lost even after Use cloud. A stored row whose lineups are still drawn is written back as stored, never deleted. - Keep both discards nothing unless the waiting work, the page and the copy's save all still hold: an edit or a page switch while the cloud version loads stops it before any copy is made, and a copy that failed to save stops it before anything is discarded. It resolves only the conflicts it copied, and pressing it again never copies twice. - The conflict popover tells the sides' changes apart only while the version both started from is the one the refused change was made against; once the page is drawn again it lists how the user's version differs from the cloud's instead. Co-Authored-By: Claude Opus 5.5 (1M context) --- lib/collab/cloud_lineup_rows.dart | 151 ++++--- .../active_page_live_sync_provider.dart | 43 +- .../collab/lineup_conflicts_provider.dart | 60 ++- .../strategy_page_session_provider.dart | 92 +++- lib/widgets/cloud_sync_button.dart | 16 +- test/cloud_lineup_rows_test.dart | 70 ++- test/strategy_page_session_provider_test.dart | 400 +++++++++++++++++- test/widgets/cloud_sync_button_test.dart | 46 +- 8 files changed, 745 insertions(+), 133 deletions(-) diff --git a/lib/collab/cloud_lineup_rows.dart b/lib/collab/cloud_lineup_rows.dart index 6c63fbb8..f4d3ed9d 100644 --- a/lib/collab/cloud_lineup_rows.dart +++ b/lib/collab/cloud_lineup_rows.dart @@ -5,14 +5,16 @@ import 'package:uuid/uuid.dart'; /// One cloud row per lineup group: lineups that share a standing spot or a /// landing spot, stored together with those spots. /// -/// A shared spot lives in exactly one row, so it can never disagree with +/// A shared spot lives in its group's row, so it cannot disagree with /// itself, and the row's one revision guards every lineup on it: two /// clients changing the same group meet as an ordinary conflict. A row never /// names anything outside itself. /// -/// A group only grows or merges, never splits: deleting the lineup that -/// joined two halves leaves both halves in the row. That keeps every edit a -/// write to exactly the row the user's lineups already live in. +/// A lineup stays in the group it was first written to: deleting the lineup +/// that joined two halves leaves both halves in the row, and groups never +/// merge. That keeps every edit a write to exactly the row the user's +/// lineups already live in, and no row is ever deleted except by deleting +/// its last lineup. class CloudLineupRow { const CloudLineupRow({required this.publicId, required this.payload}); @@ -44,10 +46,10 @@ class CloudLineupRows { /// A page's lineups as its live [rows] describe them, rows in order. /// -/// Ids are unique within a row but nothing stops two rows naming the same -/// one (two clients merging groups at once, say). The first row to name a -/// spot or lineup draws it; a lineup in a later row aimed at that spot is -/// drawn to it too. +/// Ids are unique within a row, and a lineup is in one row. A spot can be +/// in two (see [cloudLineupRows]): the first row to name a spot or lineup +/// draws it, and a lineup in a later row aimed at that spot is drawn to it +/// too. /// /// Throws a [FormatException] naming the row when one cannot be read. CloudLineups lineUpGraphFromCloudRows(Iterable rows) { @@ -87,14 +89,20 @@ CloudLineups lineUpGraphFromCloudRows(Iterable rows) { /// before ([groupOf], from the rows the canvas was drawn from and earlier /// calls). /// -/// Lineups connected through a spot share a row, and so do lineups that -/// shared one before, so a group never splits. A group connected to two -/// groups (only a merge from two clients makes one) takes the smaller id -/// and the other row is no longer written. A group with no lineup that was -/// in one before is new: it takes its smallest lineup id that is not +/// A lineup stays in the group it was first written to, so a group never +/// splits, and no row is ever deleted to tidy groups up: a group's row goes +/// only when its last lineup does. New lineups that share a spot form one +/// component, which joins the group one of its spots is already in (the +/// smallest such group's id, when spots of two groups meet in it), or else +/// is a new group. A new group takes its smallest lineup id that is not /// already a group's id, here, in [groupOf] or in [takenGroupIds] (or a /// fresh id, when every one of them is). /// +/// A row holds its lineups and every spot they aim at. The app only ever +/// aims a new lineup at one existing spot, so a spot is in one row; when +/// lineups of two groups do share one (only rows written elsewhere make +/// that), each row carries it, rather than one group swallowing the other. +/// /// A lineup whose origin or landing is missing draws nothing on the canvas, /// so it is not written; neither is a spot no lineup is aimed at. Rows come /// in the order of their first lineup, their contents in [graph]'s order. @@ -112,8 +120,22 @@ CloudLineupRows cloudLineupRows( link, ]; - // Union-find over lineups, spots and earlier groups, each kind in its own - // namespace so equal ids of different kinds stay apart. + // The group of each lineup already in one, and so of the spots they aim + // at, as far as [groupOf] does not already say. + final linkGroup = { + for (final link in links) + if (groupOf[link.id] case final group?) link.id: group, + }; + final spotGroup = {}; + for (final link in links) { + for (final spot in ['o:${link.originId}', 'l:${link.landingId}']) { + final group = groupOf[spot.substring(2)] ?? linkGroup[link.id]; + if (group != null) spotGroup.putIfAbsent(spot, () => group); + } + } + + // New lineups, joined through the spots they share: each kind of id in + // its own namespace, so an origin and a landing with one id stay apart. final parent = {}; String find(String node) { final up = parent[node] ?? node; @@ -122,84 +144,79 @@ CloudLineupRows cloudLineupRows( } void union(String a, String b) { - parent.putIfAbsent(a, () => a); - parent.putIfAbsent(b, () => b); final rootA = find(a); final rootB = find(b); if (rootA != rootB) parent[rootA] = rootB; } - void joinEarlierGroup(String node, String id) { - if (groupOf[id] case final group?) union(node, 'g:$group'); - } - - for (final link in links) { - final node = 'k:${link.id}'; - union(node, 'o:${link.originId}'); - union(node, 'l:${link.landingId}'); - joinEarlierGroup(node, link.id); - joinEarlierGroup('o:${link.originId}', link.originId); - joinEarlierGroup('l:${link.landingId}', link.landingId); - } - - // Each component's links in graph order, and the earlier groups in it. - final linksByRoot = >{}; - for (final link in links) { - (linksByRoot[find('k:${link.id}')] ??= []).add(link); + final newLinks = [ + for (final link in links) + if (!linkGroup.containsKey(link.id)) link, + ]; + for (final link in newLinks) { + union('k:${link.id}', 'o:${link.originId}'); + union('k:${link.id}', 'l:${link.landingId}'); } - final earlierByRoot = >{}; - for (final node in parent.keys) { - if (node.startsWith('g:')) { - (earlierByRoot[find(node)] ??= []).add(node.substring(2)); + final componentLinks = >{}; + final componentGroups = >{}; + for (final link in newLinks) { + final root = find('k:${link.id}'); + (componentLinks[root] ??= []).add(link); + for (final spot in ['o:${link.originId}', 'l:${link.landingId}']) { + if (spotGroup[spot] case final group?) { + (componentGroups[root] ??= {}).add(group); + } } } String smallest(Iterable ids) => ids.reduce((a, b) => a.compareTo(b) <= 0 ? a : b); - - final idByRoot = {}; - final used = {...takenGroupIds, ...groupOf.values}; - for (final MapEntry(key: root, value: earlier) in earlierByRoot.entries) { - if (!linksByRoot.containsKey(root)) continue; - final id = smallest(earlier); - idByRoot[root] = id; - used.addAll(earlier); - } - for (final MapEntry(key: root, value: groupLinks) in linksByRoot.entries) { - if (idByRoot.containsKey(root)) continue; - final candidates = [for (final link in groupLinks) link.id]..sort(); - final id = candidates.firstWhere( - (candidate) => !used.contains(candidate), - orElse: () => const Uuid().v4(), - ); - idByRoot[root] = id; - used.add(id); + final used = { + ...takenGroupIds, + ...groupOf.values, + ...linkGroup.values, + }; + for (final MapEntry(key: root, value: component) in componentLinks.entries) { + final String group; + if (componentGroups[root] case final groups?) { + group = smallest(groups); + } else { + final candidates = [for (final link in component) link.id]..sort(); + group = candidates.firstWhere( + (candidate) => !used.contains(candidate), + orElse: () => const Uuid().v4(), + ); + used.add(group); + } + for (final link in component) { + linkGroup[link.id] = group; + } } - final originsById = {for (final origin in graph.origins) origin.id: origin}; - final landingsById = { - for (final landing in graph.landings) landing.id: landing, - }; + final linksByGroup = >{}; + for (final link in links) { + (linksByGroup[linkGroup[link.id]!] ??= []).add(link); + } final assigned = {}; final rows = []; - for (final MapEntry(key: root, value: groupLinks) in linksByRoot.entries) { - final id = idByRoot[root]!; + for (final MapEntry(key: id, value: groupLinks) in linksByGroup.entries) { final usedOrigins = {for (final link in groupLinks) link.originId}; final usedLandings = {for (final link in groupLinks) link.landingId}; final origins = [ for (final origin in graph.origins) - if (usedOrigins.contains(origin.id)) originsById[origin.id]!, + if (usedOrigins.contains(origin.id)) origin, ]; final landings = [ for (final landing in graph.landings) - if (usedLandings.contains(landing.id)) landingsById[landing.id]!, + if (usedLandings.contains(landing.id)) landing, ]; - for (final itemId in [...usedOrigins, ...usedLandings]) { - assigned[itemId] = id; - } for (final link in groupLinks) { assigned[link.id] = id; } + // A spot carried by two rows stays with the group it was in. + for (final spot in [...usedOrigins, ...usedLandings]) { + assigned.putIfAbsent(spot, () => groupOf[spot] ?? id); + } rows.add( CloudLineupRow( publicId: id, diff --git a/lib/providers/collab/active_page_live_sync_provider.dart b/lib/providers/collab/active_page_live_sync_provider.dart index ead7b12f..36af5964 100644 --- a/lib/providers/collab/active_page_live_sync_provider.dart +++ b/lib/providers/collab/active_page_live_sync_provider.dart @@ -103,11 +103,15 @@ class ActivePageLiveSyncNotifier extends Notifier { } /// The server's version of [key] the canvas was drawn from, which local - /// edits to it were made against: its payload, or null when the canvas was - /// drawn without it or with it deleted. - Object? hydratedBasePayload(EntitySyncKey key) { + /// edits to it were made against: its revision, and its payload (null + /// when it was deleted). Null when the canvas was drawn without it. + ({int revision, Object? payload})? hydratedBase(EntitySyncKey key) { final base = _hydratedBaseByEntityKey[key]; - return base == null || base.deleted ? null : base.payload; + if (base == null) return null; + return ( + revision: base.revision, + payload: base.deleted ? null : base.payload + ); } /// The lineup group [itemId] (a lineup, origin or landing) on [pageId] @@ -1022,8 +1026,9 @@ class ActivePageLiveSyncNotifier extends Notifier { // has or had, so it never lands on a teammate's group or a deleted one. final freshLineupSortIndex = freshSortIndexes(EntitySyncKeyKind.lineup); final lineupGroupOf = _lineupGroupOfByPage[pageId] ??= {}; + final lineUpGraph = ref.read(lineUpProvider).graph; final lineupRows = cloudLineupRows( - ref.read(lineUpProvider).graph, + lineUpGraph, groupOf: lineupGroupOf, takenGroupIds: { for (final key in [ @@ -1046,6 +1051,34 @@ class ActivePageLiveSyncNotifier extends Notifier { deleted: false, ); } + // A group's row goes only when its last lineup does. A stored row none + // of the rows above is, while a lineup it holds is still drawn, had its + // lineups drawn from another row naming them too (only rows a build + // that merged groups wrote do that): it stays exactly as stored, never + // deleted and never rewritten with the other row's copy. + final drawnLineupIds = {for (final link in lineUpGraph.links) link.id}; + for (final MapEntry(:key, value: base) + in _hydratedBaseByEntityKey.entries) { + if (key.pageId != pageId || + key.kind != EntitySyncKeyKind.lineup || + base.deleted || + entities.containsKey(key)) { + continue; + } + final links = cloudPayloadData(base.payload)['links']; + final holdsDrawn = links is List && + links.any( + (link) => link is Map && drawnLineupIds.contains(link['id'])); + if (!holdsDrawn) continue; + entities[key] = _NormalizedEntity( + key: key, + overlayEntityType: ActivePageOverlayEntityType.lineup, + payload: base.payload, + sortIndex: knownSortIndex(key) ?? base.sortIndex, + revision: 0, + deleted: false, + ); + } return entities; } diff --git a/lib/providers/collab/lineup_conflicts_provider.dart b/lib/providers/collab/lineup_conflicts_provider.dart index 61fc0b04..5d20cd54 100644 --- a/lib/providers/collab/lineup_conflicts_provider.dart +++ b/lib/providers/collab/lineup_conflicts_provider.dart @@ -19,11 +19,14 @@ class LineupGroupConflict { final EntitySyncKey key; - /// The user's waiting version, against the version it was made from. + /// The user's waiting version against the version it was made from, or, + /// when [cloud] is null, against the cloud's version. final List yours; - /// The cloud's version, against the same one. - final List cloud; + /// The cloud's version against the one the user's was made from. Null + /// when that version is no longer on this device (the page was drawn + /// again since): then who changed what cannot be told apart. + final List? cloud; } /// What the active strategy's refused work does to its lineup groups, when @@ -33,8 +36,12 @@ class LineupGroupConflict { /// /// Every version is read from what this device already has: the user's /// newest waiting change, the server's version the canvas was drawn from, -/// and the live copy of the page. When one of them cannot be read, there is -/// nothing trustworthy to show, so this is null too. +/// and the live copy of the page. The version drawn from tells the sides' +/// changes apart only while it is the one the refused change was made +/// against; once the page is drawn again it is the cloud's, so every +/// conflict then lists only how the user's version differs from the +/// cloud's. When a version cannot be read, there is nothing trustworthy to +/// show, so this is null too. final lineupConflictsProvider = Provider?>((ref) { final queue = ref.watch(strategyOpQueueProvider); final attention = queue.attentionByEntityKey; @@ -49,32 +56,51 @@ final lineupConflictsProvider = Provider?>((ref) { final liveSync = ref.read(activePageLiveSyncProvider.notifier); final cloudRows = {for (final row in page.lineups) row.publicId: row}; - final conflicts = []; + final versions = <(EntitySyncKey, LineUpGraph, LineUpGraph, LineUpGraph)>[]; + var basesHold = true; for (final MapEntry(:key, value: refused) in attention.entries) { - final basePayload = liveSync.hydratedBasePayload(key); + final base = liveSync.hydratedBase(key); + final refusedOp = refused.pending.op; + final madeAgainst = switch (refusedOp) { + LineupAddOp(:final expectedLineupRevision) => expectedLineupRevision, + LineupPatchOp(:final expectedLineupRevision) => expectedLineupRevision, + LineupDeleteOp(:final expectedLineupRevision) => expectedLineupRevision, + LineupReorderOp(:final expectedLineupRevision) => expectedLineupRevision, + _ => null, + }; + if (base?.revision != madeAgainst) basesHold = false; final newest = (queue.successorByEntityKey[key] ?? refused).pending.op; final yoursPayload = switch (newest) { LineupAddOp(:final payload) => payload, LineupPatchOp(:final payload?) => payload, LineupDeleteOp() => null, - _ => basePayload, + _ => base?.payload, }; final cloudRow = cloudRows[key.entityId]; - final base = _groupGraph(key, basePayload); final yours = _groupGraph(key, yoursPayload); final cloud = _groupGraph( key, cloudRow == null || cloudRow.deleted ? null : cloudRow.payload, ); - if (base == null || yours == null || cloud == null) return null; - conflicts.add( - LineupGroupConflict( - key: key, - yours: lineupChanges(from: base, to: yours), - cloud: lineupChanges(from: base, to: cloud), - ), - ); + final from = + base == null ? LineUpGraph.empty : _groupGraph(key, base.payload); + if (yours == null || cloud == null || from == null) return null; + versions.add((key, yours, cloud, from)); } + final conflicts = [ + for (final (key, yours, cloud, from) in versions) + basesHold + ? LineupGroupConflict( + key: key, + yours: lineupChanges(from: from, to: yours), + cloud: lineupChanges(from: from, to: cloud), + ) + : LineupGroupConflict( + key: key, + yours: lineupChanges(from: cloud, to: yours), + cloud: null, + ), + ]; return conflicts; }); diff --git a/lib/providers/strategy_page_session_provider.dart b/lib/providers/strategy_page_session_provider.dart index a6e842f0..fb49d199 100644 --- a/lib/providers/strategy_page_session_provider.dart +++ b/lib/providers/strategy_page_session_provider.dart @@ -68,8 +68,10 @@ enum KeepBothOutcome { /// The cloud's versions could not be loaded. Nothing changed. unchanged, - /// The user's versions were added as copies, but the cloud's could not be - /// loaded after all, so the conflicts still wait. Use cloud finishes. + /// The user's versions were added as copies, but something failed before + /// the conflicts were resolved (the cloud's versions stopped loading, a + /// copy could not be saved, or the waiting work changed), so they still + /// wait. copiesOnly, } @@ -179,6 +181,11 @@ class StrategyPageSessionNotifier extends Notifier { /// The latest strategy revision at which each page was seen live. final Map _pagesSeenLiveAt = {}; + /// Waiting work Keep both already put a copy of on the canvas, by + /// [_waitingWork] entry: pressing it again finishes the job instead of + /// copying the same version twice. + final Set<(EntitySyncKey, (String, String?))> _keptAsCopy = {}; + @override StrategyPageSessionState build() { ref.onDispose(() => _disposed = true); @@ -566,24 +573,31 @@ class StrategyPageSessionNotifier extends Notifier { /// that deletes the group, or only reorders it, holds nothing to copy. /// /// Only for conflicts that are all lineup groups on the page on screen - /// (see lineupConflictsProvider); anything else is a [StateError]. The - /// cloud's version is loaded first, so a device that cannot reach it - /// changes nothing. The copies then go on the canvas, and - /// [useCloudVersionsForRejected] queues them before it discards anything: - /// if taking the cloud's version still fails, the copies and the waiting - /// versions both remain, and Use cloud finishes the job. + /// (see lineupConflictsProvider); anything else is a [StateError]. + /// + /// Nothing is discarded unless everything still matches what was copied: + /// the cloud's version loads, the page on screen is the same one, the + /// waiting work is the same (an edit made meanwhile would not be in the + /// copy), and the copy itself saved. When the cloud's version cannot be + /// loaded, or the work changed before the copy was made, nothing changes. + /// When something fails after, the copy stays and the conflicts still + /// wait; pressing Keep both again resolves them without a second copy. Future keepBothForRejected() async { final strategyState = ref.read(strategyProvider); + final strategyId = strategyState.strategyId; final queue = ref.read(strategyOpQueueProvider); final attention = queue.attentionByEntityKey; final activePageId = state.activePageId; if (strategyState.source != StrategySource.cloud || + strategyId == null || attention.isEmpty || attention.keys.any((key) => key.kind != EntitySyncKeyKind.lineup || key.pageId != activePageId)) { throw StateError('Keep both is only for lineup groups on this page'); } + final generation = _pageSessionGeneration; + final waiting = _waitingWork(attention.keys); final copies = []; for (final MapEntry(:key, value: refused) in attention.entries) { final newest = (queue.successorByEntityKey[key] ?? refused).pending.op; @@ -592,33 +606,82 @@ class StrategyPageSessionNotifier extends Notifier { LineupPatchOp(:final payload?) => payload, _ => null, }; - if (payload == null) continue; + if (payload == null || _keptAsCopy.contains((key, waiting[key]!))) { + continue; + } copies.add(forkLineUpGraph(lineUpGraphFromCloudRows([ CloudLineupRow(publicId: key.entityId!, payload: payload), ]).graph)); } + bool unchangedSince() => + !_disposed && + generation == _pageSessionGeneration && + _sameWaitingWork(waiting); await ref.read(remoteEditorSnapshotProvider.notifier).refresh(); final snapshot = ref.read(remoteEditorSnapshotProvider).valueOrNull; - if (snapshot == null || - snapshot.header.publicId != strategyState.strategyId) { + if (snapshot == null || snapshot.header.publicId != strategyId) { return KeepBothOutcome.unchanged; } + // Edits made while the cloud's version loaded are saved first; one to + // a conflicting group is newer than the copy, so nothing goes ahead. + final pageSource = _resolvePageSource(strategyId, StrategySource.cloud); + await pageSource.flushCurrentPage(); + if (!unchangedSince()) return KeepBothOutcome.unchanged; final lineUps = ref.read(lineUpProvider.notifier); for (final copy in copies) { lineUps.addRecovered(copy); } - return await useCloudVersionsForRejected() + _keptAsCopy + .addAll(waiting.entries.map((entry) => (entry.key, entry.value))); + await pageSource.flushCurrentPage(); + // A copy that could not be saved waits in attention itself. + final saved = ref + .read(strategyOpQueueProvider) + .attentionByEntityKey + .keys + .every(waiting.containsKey); + if (!saved || !unchangedSince()) return KeepBothOutcome.copiesOnly; + return await _useCloudVersionsFor(waiting) ? KeepBothOutcome.kept : KeepBothOutcome.copiesOnly; } + /// The refused op, and the newer one queued behind it if any, of each of + /// [keys]: what is waiting for the user's choice. + Map _waitingWork( + Iterable keys, + ) { + final queue = ref.read(strategyOpQueueProvider); + return { + for (final key in keys) + if (queue.attentionByEntityKey[key] case final refused?) + key: ( + refused.pending.op.opId, + queue.successorByEntityKey[key]?.pending.op.opId, + ), + }; + } + + /// Whether exactly [waiting] still waits, nothing changed or gone. + bool _sameWaitingWork(Map waiting) { + final now = _waitingWork(waiting.keys); + return now.length == waiting.length && + waiting.entries.every((entry) => now[entry.key] == entry.value); + } + /// Adopts the cloud version for every current conflict in this strategy. /// /// The authoritative page is loaded before any local intent is discarded. /// A failed load therefore leaves the durable conflict available to retry. - Future useCloudVersionsForRejected() async { + Future useCloudVersionsForRejected() => _useCloudVersionsFor(null); + + /// [useCloudVersionsForRejected], for exactly the conflicts in [waiting] + /// when given: if they no longer wait as they were, nothing is discarded. + Future _useCloudVersionsFor( + Map? waiting, + ) async { final strategyState = ref.read(strategyProvider); final strategyId = strategyState.strategyId; if (strategyState.source != StrategySource.cloud || strategyId == null) { @@ -632,9 +695,10 @@ class StrategyPageSessionNotifier extends Notifier { final strategyNotifier = ref.read(strategyProvider.notifier); strategyNotifier.consumeScheduledCloudPageSync(); strategyNotifier.consumeScheduledCloudStrategySync(); + if (waiting != null && !_sameWaitingWork(waiting)) return false; final rejected = Map.from( ref.read(strategyOpQueueProvider).attentionByEntityKey, - ); + )..removeWhere((key, _) => waiting != null && !waiting.containsKey(key)); if (rejected.isEmpty) return true; await ref.read(remoteEditorSnapshotProvider.notifier).refresh(); diff --git a/lib/widgets/cloud_sync_button.dart b/lib/widgets/cloud_sync_button.dart index 9384f34c..b6df1dd7 100644 --- a/lib/widgets/cloud_sync_button.dart +++ b/lib/widgets/cloud_sync_button.dart @@ -209,8 +209,7 @@ class _CloudSyncButtonState extends ConsumerState { 'Nothing was changed.'; case KeepBothOutcome.copiesOnly: resolutionError = 'Your version was added as a copy, but the ' - 'cloud version could not be loaded. Choose Use cloud to ' - 'finish.'; + 'conflict is still open.'; } } catch (error, stackTrace) { log( @@ -685,7 +684,18 @@ class _LineupConflictList extends StatelessWidget { @override Widget build(BuildContext context) { final yours = [for (final conflict in conflicts) ...conflict.yours]; - final cloud = [for (final conflict in conflicts) ...conflict.cloud]; + // Without the version both sides started from, only the difference + // between them can be shown, not who made it. + if (conflicts.any((conflict) => conflict.cloud == null)) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 10), + _section(context, 'How your version differs from the cloud', yours), + ], + ); + } + final cloud = [for (final conflict in conflicts) ...?conflict.cloud]; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/test/cloud_lineup_rows_test.dart b/test/cloud_lineup_rows_test.dart index d8113f89..91dfa596 100644 --- a/test/cloud_lineup_rows_test.dart +++ b/test/cloud_lineup_rows_test.dart @@ -177,7 +177,7 @@ void main() { expect(written.groupOf['c'], 'k9'); }); - test('a lineup joining two groups merges them under the smaller id', () { + test('a lineup joining two groups goes to one, and neither row goes', () { final read = lineUpGraphFromCloudRows(cloudLineupRows(_page()).rows); final bridged = LineUpGraph( origins: read.graph.origins, @@ -185,9 +185,64 @@ void main() { links: [...read.graph.links, _link('k5', 'z', 'shared')], ); - final rows = cloudLineupRows(bridged, groupOf: read.groupOf).rows; - expect(rows.map((row) => row.publicId), ['k1']); - expect(_ids(_data(rows.single)['links']), ['k1', 'k2', 'k3', 'k9', 'k5']); + final written = cloudLineupRows(bridged, groupOf: read.groupOf); + // k5 joins the smaller group, k1, and carries origin z with it; k9's + // row keeps z too, and nothing is deleted. + expect(written.rows.map((row) => row.publicId), ['k1', 'k9']); + expect(_ids(_data(written.rows.first)['links']), ['k1', 'k2', 'k3', 'k5']); + expect(_ids(_data(written.rows.first)['origins']), ['a', 'b', 'z']); + expect(_ids(_data(written.rows.last)['links']), ['k9']); + expect(_ids(_data(written.rows.last)['origins']), ['z']); + expect(written.groupOf['z'], 'k9', reason: 'a spot stays in its group'); + expect(written.groupOf['k5'], 'k1'); + }); + + test('a lineup two rows name is drawn from the first, and kept there', () { + // A build that merged groups wrote k9 into k1's row, and was refused + // deleting k9's row; a teammate has edited k9 there since. Live sync + // writes k9's row back as stored while k9 is drawn (see + // _normalizedLocalEntities); the codec only draws one k9. + CloudLineupRow row(String id, LineUpGraph graph) => CloudLineupRow( + publicId: id, + payload: cloudLineupsPayload({ + 'id': id, + 'origins': [for (final origin in graph.origins) origin.toJson()], + 'landings': [ + for (final landing in graph.landings) landing.toJson(), + ], + 'links': [for (final link in graph.links) link.toJson()], + }), + ); + final merged = row( + 'k1', + LineUpGraph( + origins: [_origin('a'), _origin('z')], + landings: [_landing('shared'), _landing('far')], + links: [ + _link('k1', 'a', 'shared'), + _link('k9', 'z', 'far').copyWith(notes: 'old'), + ], + ), + ); + final teammates = row( + 'k9', + LineUpGraph( + origins: [_origin('z')], + landings: [_landing('far')], + links: [_link('k9', 'z', 'far').copyWith(notes: 'newer')], + ), + ); + + final read = lineUpGraphFromCloudRows([merged, teammates]); + expect(read.graph.links.map((link) => link.id), ['k1', 'k9']); + expect(read.graph.links.last.notes, 'old'); + expect(read.groupOf['k9'], 'k1'); + expect( + cloudLineupRows(read.graph, groupOf: read.groupOf) + .rows + .map((row) => row.publicId), + ['k1'], + ); }); test('a new group never takes a group id already in use', () { @@ -247,9 +302,12 @@ void main() { expect(read.graph.links.map((l) => l.landingId), ['shared', 'shared']); expect(read.groupOf['shared'], 'k1'); - // Written back, they become one group: the spot connects them. + // Written back, both rows stay, each carrying the spot as drawn: no + // lineup moves rows and no row is deleted. final rows = cloudLineupRows(read.graph, groupOf: read.groupOf).rows; - expect(rows.map((row) => row.publicId), ['k1']); + expect(rows.map((row) => row.publicId), ['k1', 'k2']); + expect(_ids(_data(rows.last)['links']), ['k2']); + expect(_ids(_data(rows.last)['landings']), ['shared']); }); test('a row of any other shape fails loudly, naming the row', () { diff --git a/test/strategy_page_session_provider_test.dart b/test/strategy_page_session_provider_test.dart index abab16a0..47331bdc 100644 --- a/test/strategy_page_session_provider_test.dart +++ b/test/strategy_page_session_provider_test.dart @@ -109,10 +109,22 @@ class _FakeRemoteEditorNotifier extends RemoteEditorSnapshotNotifier { /// While set, a refresh reads nothing, as a read refused or cut off. bool readFails = false; + /// While set, a refresh reads nothing when this holds for its count. + bool Function(int refreshCount)? readFailsWhen; + + /// While set, the next refresh waits for it, then reads [initialSnapshot] + /// as it is by then: a read still on its way. + Completer? refreshGate; + @override Future refresh() async { refreshCount += 1; - state = readFails ? const AsyncData(null) : AsyncData(initialSnapshot); + final count = refreshCount; + final gate = refreshGate; + refreshGate = null; + await gate?.future; + final fails = readFails || (readFailsWhen?.call(count) ?? false); + state = fails ? const AsyncData(null) : AsyncData(initialSnapshot); } @override @@ -406,6 +418,20 @@ class _FakeStrategyOpQueueNotifier extends StrategyOpQueueNotifier { } } +/// An outbox whose writes fail while [failPut] holds for the record, as a +/// full or locked disk. +class _FailingOutboxStore extends MemoryDurableStrategyOutboxStore { + bool Function(DurableOutboxRecord record)? failPut; + + @override + Future put(DurableOutboxRecord record) async { + if (failPut?.call(record) ?? false) { + throw const FileSystemException('The disk is full.'); + } + await super.put(record); + } +} + /// Plays the server's page restore: [onRestore] runs for each call, and /// may throw as the server would. class _RestoringRepository implements ConvexStrategyRepository { @@ -5076,6 +5102,61 @@ void main() { await _settle(); }); + test( + 'a row an older build merged a group into never costs the merged ' + 'group its row', () async { + final page = _page('page-1', 0); + // A build that merged groups wrote k9's lineup into k1's row and was + // refused deleting k9's; a teammate has edited k9's lineup since. + final merged = _groupRow( + page.publicId, + 'link-1', + origins: [_originJson('a'), _originJson('z', const Offset(70, 70))], + landings: [_landingJson('shared'), _landingJson('far')], + links: [ + _linkJson('link-1', originId: 'a', landingId: 'shared'), + _linkJson('link-5', originId: 'z', landingId: 'shared'), + _linkJson('link-9', originId: 'z', landingId: 'far', notes: 'old'), + ], + revision: 2, + ); + final teammates = _groupRow( + page.publicId, + 'link-9', + origins: [_originJson('z', const Offset(70, 70))], + landings: [_landingJson('far')], + links: [ + _linkJson('link-9', originId: 'z', landingId: 'far', notes: 'newer') + ], + revision: 3, + sortIndex: 1, + ); + final container = await openCloudPage(page, [merged, teammates]); + + // An unrelated edit writes no lineup row, and deletes none. + expect(lineupOpsAfterTextEdit(container, page), isEmpty); + + // Nor does an edit to the merged group delete the row it merged. + container.read(lineUpProvider.notifier).updateLink(container + .read(lineUpProvider) + .linkById('link-1')! + .copyWith(notes: 'mine')); + final ops = { + for (final MapEntry(:key, :value) in container + .read(activePageLiveSyncProvider.notifier) + .syncLocalPage( + strategyPublicId: 'cloud-strategy', + pageId: page.publicId, + )! + .entries) + if (key.kind == EntitySyncKeyKind.lineup) key: value, + }; + expect(ops.values.whereType(), isEmpty); + expect(ops.keys, + isNot(contains(EntitySyncKey.lineup(page.publicId, 'link-9')))); + await _settle(); + }); + test('a Paranoia saved at the old size opens corrected and sends nothing', () async { final page = _page('page-1', 0); @@ -6388,6 +6469,10 @@ void main() { /// Shows the page as [server] now holds it, as the live read would. late void Function() showServer; + /// Has the live read show [pageId] from now on, as it does once the + /// session selects that page. + late void Function(String pageId) readPage; + /// Opens [page] on the real queue, its outbox kept in [store], which /// sends to [server] through [repository] and reads it back after every /// batch. A batch waits for [hold] while it is set. The queue reaches @@ -6397,22 +6482,44 @@ void main() { RemotePage page, { DurableStrategyOutboxStore? store, Completer? hold, + List otherPages = const [], }) async { + final others = { + for (final other in otherPages) + other.publicId: _pageSnapshot(other, settings: settingsFor(1)), + }; + var shown = page.publicId; RemoteEditorSnapshot read() => _editorSnapshot( - pages: [page], - activePage: _pageSnapshot( - page, - settings: settingsFor(1), - elements: server.elements, - lineups: server.rows, - ), + pages: [page, ...otherPages], + activePage: others[shown] ?? + _pageSnapshot( + page, + settings: settingsFor(1), + elements: server.elements, + lineups: server.rows, + ), ); - final remote = liveRead = _FakeRemoteEditorNotifier(read()); - showServer = () => remote.setSnapshot(read()); - repository = _ServerRepository( - server, - afterBatch: () => remote.initialSnapshot = read(), - )..hold = hold; + final remote = liveRead = _FakeRemoteEditorNotifier(read(), pageCatalog: { + page.publicId: read().activePage!, + ...others, + }); + void reread() { + final shownBefore = shown; + shown = page.publicId; + remote.pageCatalog[page.publicId] = read().activePage!; + shown = shownBefore; + remote.initialSnapshot = read(); + } + + showServer = () { + reread(); + remote.setSnapshot(remote.initialSnapshot); + }; + readPage = (pageId) { + shown = pageId; + reread(); + }; + repository = _ServerRepository(server, afterBatch: reread)..hold = hold; final container = ProviderContainer(overrides: [ remoteEditorSnapshotProvider.overrideWith(() => remote), durableStrategyOutboxStoreProvider @@ -6867,10 +6974,15 @@ void main() { ProviderContainer, List>, MemoryDurableStrategyOutboxStore, - )> refusedBesideTeammate(RemotePage page) async { + )> refusedBesideTeammate( + RemotePage page, { + MemoryDurableStrategyOutboxStore? store, + List otherPages = const [], + }) async { server = _FakeServer(page.publicId, lineups: [fanIn(page.publicId)]); - final store = MemoryDurableStrategyOutboxStore(); - final (container, batches) = await openOnRealQueue(page, store: store); + store ??= MemoryDurableStrategyOutboxStore(); + final (container, batches) = + await openOnRealQueue(page, store: store, otherPages: otherPages); final key = keyOf(page, 'link-a'); // Both clients start from revision 7; the teammate's save lands first. @@ -6924,7 +7036,7 @@ void main() { final conflict = container.read(lineupConflictsProvider)!.single; expect(conflict.key, key); expect(described(conflict.yours), ['Heaven: notes edited']); - expect(described(conflict.cloud), ['Mid: notes edited']); + expect(described(conflict.cloud!), ['Mid: notes edited']); }); test( @@ -7282,6 +7394,258 @@ void main() { expect(container.read(strategyConflictProvider), hasLength(1)); }); + /// The lineups on screen, each as `name: notes`. + List onScreenOf(ProviderContainer container) => [ + for (final link in container.read(lineUpProvider).links) + '${link.name}: ${link.notes}', + ]; + + test( + 'Keep both whose copy cannot be saved discards nothing: the copy and ' + 'the waiting work both stay', () async { + final page = _page('page-1', 0); + final store = _FailingOutboxStore(); + final (container, batches, _) = + await refusedBesideTeammate(page, store: store); + final key = keyOf(page, 'link-a'); + StrategyOpQueueState queueState() => + container.read(strategyOpQueueProvider); + final waiting = queueState().attentionByEntityKey[key]!.pending.op.opId; + // The copy's outbox write fails; the waiting work's record is kept. + store.failPut = (record) => record.entityKey != key; + + final outcome = await container + .read(strategyPageSessionProvider.notifier) + .keepBothForRejected(); + for (var i = 0; i < 10; i++) { + await _settle(); + } + + expect(outcome, KeepBothOutcome.copiesOnly); + // The original still waits, in memory and on disk, as it was. + expect(queueState().attentionByEntityKey[key]!.pending.op.opId, waiting); + expect( + store.load().records.singleWhere((r) => r.entityKey == key).status, + DurableOutboxStatus.attention, + ); + expect(sentFor(batches, key), hasLength(1)); + // The copy is still on screen beside the user's version; the cloud's + // version was not taken. + expect( + onScreenOf(container), + [ + 'Heaven: mine', + 'Mid: remote lineup', + 'Heaven: mine', + 'Mid: remote lineup' + ], + ); + // The server has only the teammate's version; nothing was discarded. + expect(server.liveRows.map((row) => row.publicId), ['link-a']); + expect( + lineupsIn(server.liveRows), ['Heaven: remote lineup', 'Mid: theirs']); + expect(queueState().hasDurabilityFailure, isTrue); + }); + + test( + 'an edit to the conflicting group while Keep both loads the cloud ' + 'version stops it: no copy, and the newer edit still waits', () async { + final page = _page('page-1', 0); + final (container, _, store) = await refusedBesideTeammate(page); + final key = keyOf(page, 'link-a'); + StrategyOpQueueState queueState() => + container.read(strategyOpQueueProvider); + final refreshes = liveRead.refreshCount; + final gate = liveRead.refreshGate = Completer(); + + final keepBoth = container + .read(strategyPageSessionProvider.notifier) + .keepBothForRejected(); + await _until(() => liveRead.refreshCount > refreshes); + // While the cloud's version is on its way, the user edits the group. + container.read(lineUpProvider.notifier).updateLink(container + .read(lineUpProvider) + .linkById('link-b')! + .copyWith(notes: 'newer')); + gate.complete(); + + expect(await keepBoth, KeepBothOutcome.unchanged); + for (var i = 0; i < 10; i++) { + await _settle(); + } + + // No copy was made of the version the edit replaced... + expect(onScreenOf(container), ['Heaven: mine', 'Mid: newer']); + expect(server.liveRows.map((row) => row.publicId), ['link-a']); + // ...and the newer edit waits behind the refused one, for the user. + expect(queueState().attentionByEntityKey.keys, [key]); + final newer = queueState().successorByEntityKey[key]!.pending.op; + expect( + _linkIn(newer.payload as CloudPayload, 'link-b')['notes'], 'newer'); + expect(_linkIn(newer.payload as CloudPayload, 'link-a')['notes'], 'mine'); + expect( + store.load().records.singleWhere((r) => r.entityKey == key).status, + DurableOutboxStatus.attention, + ); + }); + + test( + 'switching page while Keep both loads the cloud version stops it: ' + 'no copy on either page', () async { + final page = _page('page-1', 0); + final other = _page('page-2', 1); + final (container, _, _) = + await refusedBesideTeammate(page, otherPages: [other]); + final key = keyOf(page, 'link-a'); + final session = container.read(strategyPageSessionProvider.notifier); + StrategyOpQueueState queueState() => + container.read(strategyOpQueueProvider); + bool copyQueued() => queueState().pending.any((pending) => + pending.op is LineupAddOp || + EntitySyncKey.forStrategyOp(pending.op)?.pageId == other.publicId); + final refreshes = liveRead.refreshCount; + final gate = liveRead.refreshGate = Completer(); + + final keepBoth = session.keepBothForRejected(); + await _until(() => liveRead.refreshCount > refreshes); + // While the cloud's version is on its way, the user opens page 2. + await session.setActivePage(other.publicId); + readPage(other.publicId); + gate.complete(); + + expect(await keepBoth, KeepBothOutcome.unchanged); + for (var i = 0; i < 10; i++) { + await _settle(); + } + expect(container.read(strategyPageSessionProvider).activePageId, + other.publicId); + expect(container.read(lineUpProvider).links, isEmpty); + expect(copyQueued(), isFalse); + + // Back on page 1: the user's version, and no copy of it. + await session.setActivePage(page.publicId); + readPage(page.publicId); + for (var i = 0; i < 10; i++) { + await _settle(); + } + expect(onScreenOf(container), ['Heaven: mine', 'Mid: remote lineup']); + expect(copyQueued(), isFalse); + expect(server.liveRows.map((row) => row.publicId), ['link-a']); + expect(queueState().attentionByEntityKey.keys, [key]); + }); + + test( + 'once the page is drawn again, the conflict lists how the user\'s ' + 'version differs from the cloud\'s, not who changed what', () async { + final page = _page('page-1', 0); + final other = _page('page-2', 1); + final (container, _, _) = + await refusedBesideTeammate(page, otherPages: [other]); + final key = keyOf(page, 'link-a'); + final session = container.read(strategyPageSessionProvider.notifier); + expect(container.read(lineupConflictsProvider)!.single.cloud, isNotNull); + + // The user leaves the page and comes back while the conflict waits: + // the page is drawn from the cloud's version, revision 8. + await session.setActivePage(other.publicId); + readPage(other.publicId); + await _settle(); + await session.setActivePage(page.publicId); + readPage(page.publicId); + for (var i = 0; i < 10; i++) { + await _settle(); + } + expect( + container + .read(activePageLiveSyncProvider.notifier) + .hydratedBase(key) + ?.revision, + 8, + ); + + final conflict = container.read(lineupConflictsProvider)!.single; + expect(conflict.key, key); + // Against revision 8, the teammate's notes on Mid would read as the + // user's, so the sides are not told apart: only the difference. + expect(conflict.cloud, isNull); + expect(described(conflict.yours), + ['Heaven: notes edited', 'Mid: notes edited']); + expect( + described(conflict.yours), + described(lineupChanges( + from: lineUpGraphFromRemoteLineups([server.row('link-a')]), + to: lineUpGraphFromCloudRows([ + CloudLineupRow( + publicId: 'link-a', + payload: container + .read(strategyOpQueueProvider) + .attentionByEntityKey[key]! + .pending + .op + .payload as CloudPayload, + ), + ]).graph, + )), + ); + }); + + test( + 'Keep both pressed again after it copied but could not resolve ' + 'finishes without a second copy', () async { + final page = _page('page-1', 0); + final (container, batches, store) = await refusedBesideTeammate(page); + final key = keyOf(page, 'link-a'); + final session = container.read(strategyPageSessionProvider.notifier); + StrategyOpQueueState queueState() => + container.read(strategyOpQueueProvider); + List copies() => [ + for (final row in server.liveRows) + if (row.publicId != 'link-a') row, + ]; + + // The first read, before the copy, works; the cloud's version then + // stops loading. + final first = liveRead.refreshCount + 1; + liveRead.readFailsWhen = (count) => count > first; + expect(await session.keepBothForRejected(), KeepBothOutcome.copiesOnly); + // The copy goes out; the waiting work stays. + await _until(() => + queueState().queuedByEntityKey.isEmpty && + queueState().inFlightByEntityKey.isEmpty && + server.liveRows.length == 2); + for (var i = 0; i < 10; i++) { + await _settle(); + } + expect(copies(), hasLength(1)); + expect(queueState().attentionByEntityKey.keys, [key]); + + // Pressed again with the cloud reachable, it only resolves. + liveRead.readFailsWhen = null; + expect(await session.keepBothForRejected(), KeepBothOutcome.kept); + await _until(() => queueState().pending.isEmpty); + for (var i = 0; i < 10; i++) { + await _settle(); + } + + final copy = copies().single; + expect(lineupsIn([copy]), ['Heaven: mine', 'Mid: remote lineup']); + expect(sentFor(batches, keyOf(page, copy.publicId)), hasLength(1)); + expect(lineupsIn([server.row('link-a')]), + ['Heaven: remote lineup', 'Mid: theirs']); + expect( + onScreenOf(container), + unorderedEquals([ + 'Heaven: remote lineup', + 'Mid: theirs', + 'Heaven: mine', + 'Mid: remote lineup', + ]), + ); + expect(queueState().attentionByEntityKey, isEmpty); + expect(queueState().needsAttention, isFalse); + expect(store.load().records, isEmpty); + }); + test( 'after Keep both, exporting the strategy to .ica and importing it ' 'brings back both versions', () async { diff --git a/test/widgets/cloud_sync_button_test.dart b/test/widgets/cloud_sync_button_test.dart index 028e5c8a..5dd82626 100644 --- a/test/widgets/cloud_sync_button_test.dart +++ b/test/widgets/cloud_sync_button_test.dart @@ -981,6 +981,46 @@ void main() { Finder line(String text) => find.text(text, findRichText: true); + testWidgets( + 'without the version both sides started from, list one section of ' + 'how yours differs from the cloud', (tester) async { + final queue = _AttentionOpQueue(1); + final session = _ConflictSession(); + final container = _createConflictContainer( + queue: queue, + session: session, + lineupConflicts: [ + LineupGroupConflict( + key: const EntitySyncKey.lineup('page-1', 'link-a'), + yours: [ + change('Heaven', 'notes edited'), + change('Mid', 'notes edited'), + ], + cloud: null, + ), + // One conflict without that version is enough to show none of + // the sides' changes apart. + LineupGroupConflict( + key: const EntitySyncKey.lineup('page-1', 'link-z'), + yours: [change('Long', 'landing moved')], + cloud: [change('Short', 'added')], + ), + ], + ); + addTearDown(container.dispose); + await openPopover(tester, container); + + expect( + find.text('How your version differs from the cloud'), findsOneWidget); + expect(line('Heaven · notes edited'), findsOneWidget); + expect(line('Mid · notes edited'), findsOneWidget); + expect(line('Long · landing moved'), findsOneWidget); + expect(find.text('Your changes'), findsNothing); + expect(find.text('Cloud changes'), findsNothing); + expect(line('Short · added'), findsNothing); + expect(find.text('Keep both'), findsOneWidget); + }); + testWidgets('list what each side changed and offer Keep both', (tester) async { final queue = _AttentionOpQueue(1); @@ -1044,8 +1084,8 @@ void main() { }); testWidgets( - 'a Keep both that copied but then lost the cloud version points to ' - 'Use cloud', (tester) async { + 'a Keep both that copied but could not resolve says the conflict ' + 'is still open', (tester) async { final queue = _AttentionOpQueue(1); final session = _ConflictSession(keepBoth: KeepBothOutcome.copiesOnly); final container = _createConflictContainer( @@ -1063,7 +1103,7 @@ void main() { find.textContaining('Your version was added as a copy'), findsOneWidget, ); - expect(find.textContaining('Choose Use cloud to finish'), findsOneWidget); + expect(find.textContaining('the conflict is still open'), findsOneWidget); }); }); From fdd8c3c8f3adf1ceb3e2e79124888e7ef4e76906 Mon Sep 17 00:00:00 2001 From: Dara Adedeji Date: Fri, 2 Oct 2026 18:56:03 -0400 Subject: [PATCH 09/14] Refuse overlapping lineup groups on the server; guard every discard From the second review of the group rows: - No two live lineup group rows of a page may hold the same lineup or spot: the server refuses such a write (INVALID_LINEUP_PAYLOAD_DATA), so rows can never disagree about one spot. The check reads a small lineupItems side table, one row per lineup and spot, kept in step with every lineup write like lineupAgents. The client-side repair of overlapping rows is gone with the state it repaired. - Discarding refused work is conditional: discardRejected takes the refused and successor op ids the user saw, and leaves a key whose work has changed since. Use cloud for Keep both saves and re-checks the waiting work and the page right before redrawing it. - Keep both is not offered, and changes nothing, while the outbox cannot save, and stops before discarding if a copy failed to save. Co-Authored-By: Claude Opus 5.5 (1M context) --- convex/lib/lineupItems.ts | 95 +++++++++ convex/lineups.test.ts | 125 +++++++++++ convex/maintenance.ts | 2 + convex/ops.ts | 12 ++ convex/schema.ts | 12 ++ convex/strategies.ts | 6 + convex/testContent.helpers.ts | 2 + lib/collab/cloud_lineup_rows.dart | 18 +- .../active_page_live_sync_provider.dart | 32 +-- .../collab/lineup_conflicts_provider.dart | 6 +- .../collab/strategy_op_queue_provider.dart | 20 +- .../strategy_page_session_provider.dart | 37 +++- test/cloud_lineup_rows_test.dart | 10 +- test/strategy_op_queue_provider_test.dart | 70 +++++++ test/strategy_page_session_provider_test.dart | 195 ++++++++++++------ 15 files changed, 525 insertions(+), 117 deletions(-) create mode 100644 convex/lib/lineupItems.ts diff --git a/convex/lib/lineupItems.ts b/convex/lib/lineupItems.ts new file mode 100644 index 00000000..43cdee40 --- /dev/null +++ b/convex/lib/lineupItems.ts @@ -0,0 +1,95 @@ +import type { Doc, Id } from "../_generated/dataModel"; +import type { MutationCtx } from "../_generated/server"; +import { errorWithCode } from "./errors"; + +type LineupPayload = Doc<"lineups">["payload"]; + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +/// The lineups and spots a lineup group row holds, each kind of id in its +/// own namespace (a landing may share its lineup's id). +export function lineupGroupItems(payload: LineupPayload): Set { + const items = new Set(); + for (const [field, kind] of [ + ["origins", "origin"], + ["landings", "landing"], + ["links", "link"], + ] as const) { + const entries = payload.data[field]; + if (!Array.isArray(entries)) continue; + for (const entry of entries) { + if (isRecord(entry) && typeof entry.id === "string") { + items.add(`${kind}:${entry.id}`); + } + } + } + return items; +} + +/// Brings the lineupItems rows of one lineup group in step with it: one per +/// lineup and spot while the group is live, none otherwise. Call after every +/// write of a lineup row, with null once the row itself is gone. +export async function syncLineupItems( + ctx: MutationCtx, + lineupId: Id<"lineups">, + lineup: Pick, "pageId" | "deleted" | "payload"> | null, +): Promise { + const existing = await ctx.db + .query("lineupItems") + .withIndex("by_lineupId", (q) => q.eq("lineupId", lineupId)) + .collect(); + const wanted = + lineup === null || lineup.deleted + ? new Set() + : lineupGroupItems(lineup.payload); + for (const row of existing) { + if (lineup !== null && wanted.delete(row.item)) { + if (row.pageId !== lineup.pageId) { + await ctx.db.patch(row._id, { pageId: lineup.pageId }); + } + continue; + } + // An item the group no longer holds, a second row for one, or a row + // that is gone. + await ctx.db.delete(row._id); + } + if (lineup === null) return; + for (const item of wanted) { + await ctx.db.insert("lineupItems", { + pageId: lineup.pageId, + lineupId, + item, + }); + } +} + +/// Refuses a lineup group row ([lineupId], or a row about to be inserted +/// when null) that would hold a lineup or spot another live row of its page +/// holds. Clients never write one: a lineup stays in its group, and a new +/// one is aimed at one existing spot at most. Two rows sharing one would +/// each carry their own copy of it, which can drift apart, so the write +/// fails loudly instead. Reads only the small lineupItems rows, never the +/// lineup rows, whose image lists can be large. +export async function assertLineupGroupAlone( + ctx: MutationCtx, + pageId: Id<"pages">, + lineupId: Id<"lineups"> | null, + payload: LineupPayload, +): Promise { + for (const item of lineupGroupItems(payload)) { + const holders = await ctx.db + .query("lineupItems") + .withIndex("by_pageId_and_item", (q) => + q.eq("pageId", pageId).eq("item", item), + ) + .take(2); + if (holders.some((holder) => holder.lineupId !== lineupId)) { + throw errorWithCode( + "INVALID_LINEUP_PAYLOAD_DATA", + "A lineup or spot in this group is already in another group", + ); + } + } +} diff --git a/convex/lineups.test.ts b/convex/lineups.test.ts index a98251f9..b37008fe 100644 --- a/convex/lineups.test.ts +++ b/convex/lineups.test.ts @@ -715,6 +715,131 @@ describe("a lineup group row is checked whole", () => { }); }); +describe("no lineup or spot is in two groups of a page", () => { + test("a new group holding a spot another group holds is refused, and nothing is stored", async () => { + const { owner } = await createHarness(); + await apply(owner, "owner-client", [ + addOp(lineup("k1", { landingId: "smoke" }), 0), + ]); + const results = await apply(owner, "owner-client", [ + addOp(lineup("k2", { landingId: "smoke" }), 1), + ]); + expect(results[0]).toMatchObject({ + status: "failed", + code: "INVALID_LINEUP_PAYLOAD_DATA", + }); + expect(liveKeys(await pageLineups(owner))).toEqual(["k1"]); + }); + + test("a patch taking a lineup another group holds is refused and leaves the row as it was", async () => { + const { owner } = await createHarness(); + await apply(owner, "owner-client", [ + addOp(lineup("k1"), 0), + addOp(lineup("k2"), 1), + ]); + // k2's row also claims k1's lineup, under its own spots. + const grabbing = group("k2", { + origins: [{ id: "k2-origin" }], + landings: [{ id: "k2-landing" }], + links: [ + { id: "k2", originId: "k2-origin", landingId: "k2-landing" }, + { id: "k1", originId: "k2-origin", landingId: "k2-landing" }, + ], + }); + const results = await apply(owner, "owner-client", [ + patchOp("grab-k1", grabbing, 1), + ]); + expect(results[0]).toMatchObject({ + status: "failed", + code: "INVALID_LINEUP_PAYLOAD_DATA", + }); + const rows = byKey(await pageLineups(owner)); + expect(rows.get("k2")).toMatchObject({ revision: 1 }); + expect(rows.get("k2")!.payload).toEqual(lineup("k2").payload); + }); + + test("ids only clash within a kind, within a page, among live rows", async () => { + const { owner } = await createHarness(); + await addSecondPage(owner, "page-2"); + await apply(owner, "owner-client", [ + addOp(lineup("k1", { landingId: "shared" }), 0), + ]); + const results = await apply(owner, "owner-client", [ + // A lineup named like another group's landing. + addOp(lineup("shared", { landingId: "other" }), 1), + // The same spot on another page. + addOp(lineup("k3", { landingId: "shared" }), 0, "page-2"), + ]); + expect(results.map((result) => result.status)).toEqual([ + "applied", + "applied", + ]); + + // Once k1's group is deleted, its spot is free for another group. + await apply(owner, "owner-client", [deleteOp("drop-k1", "k1", 1)]); + const reuse = await apply(owner, "owner-client", [ + addOp(lineup("k4", { landingId: "shared" }), 2), + ]); + expect(reuse[0]).toMatchObject({ status: "applied" }); + }); + + test("a duplicated strategy's groups hold their spots in the copy too", async () => { + const { owner } = await createHarness(); + await apply(owner, "owner-client", [addOp(lineup("k1"), 0)]); + await owner.mutation(duplicateStrategy, { + clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, + sourceStrategyPublicId: strategyPublicId, + publicId: "lineups-copy", + name: "Lineups (Copy)", + }); + const copy = (await owner.query(getFullSnapshot, { + clientProtocolVersion: CURRENT_CLOUD_PROTOCOL_VERSION, + strategyPublicId: "lineups-copy", + })) as { + pages: Array<{ publicId: string }>; + lineups: Array; + }; + const copied = copy.lineups[0]!; + const landingId = ( + copied.payload.data.landings as Array<{ id: string }> + )[0]!.id; + + const results = await apply( + owner, + "owner-client", + [addOp(lineup("k2", { landingId }), 1, copied.pagePublicId)], + "lineups-copy", + ); + expect(results[0]).toMatchObject({ + status: "failed", + code: "INVALID_LINEUP_PAYLOAD_DATA", + }); + }); + + test("restoring a deleted group over a spot another group took since is refused", async () => { + const { owner } = await createHarness(); + await apply(owner, "owner-client", [ + addOp(lineup("k1", { landingId: "smoke" }), 0), + ]); + await apply(owner, "owner-client", [deleteOp("drop-k1", "k1", 1)]); + await apply(owner, "owner-client", [ + addOp(lineup("k2", { landingId: "smoke" }), 1), + ]); + const results = await apply(owner, "owner-client", [ + { + ...addOp(lineup("k1", { landingId: "smoke" }), 0), + opId: "restore-k1", + expectedLineupRevision: 2, + }, + ]); + expect(results[0]).toMatchObject({ + status: "failed", + code: "INVALID_LINEUP_PAYLOAD_DATA", + }); + expect(liveKeys(await pageLineups(owner))).toEqual(["k2"]); + }); +}); + describe("clients on protocol 4", () => { test("a protocol 4 batch with its lineup options and a link row is told to upgrade", async () => { const { owner } = await createHarness(); diff --git a/convex/maintenance.ts b/convex/maintenance.ts index b75a04e7..1cd01f07 100644 --- a/convex/maintenance.ts +++ b/convex/maintenance.ts @@ -24,6 +24,7 @@ import { syncLineupAgents, } from "./lib/strategyAgentSummary"; import { PAGE_TRASH_RETENTION_MS } from "./lib/entities"; +import { syncLineupItems } from "./lib/lineupItems"; const MAINTENANCE_BATCH_SIZE = 200; // Content rows can each hold up to ~900 KB (the op size cap), so passes @@ -115,6 +116,7 @@ async function purgeContentRows( } if ("lineupId" in row) { await syncLineupAgents(ctx, row.lineupId, null); + await syncLineupItems(ctx, row.lineupId, null); await ctx.db.delete(row.lineupId); } else { await ctx.db.delete(row.elementId); diff --git a/convex/ops.ts b/convex/ops.ts index 246956e8..f871506b 100644 --- a/convex/ops.ts +++ b/convex/ops.ts @@ -38,6 +38,7 @@ import { cloudProtocolArgs, } from "./lib/cloudProtocol"; import { valuesEqual } from "./lib/canonicalValues"; +import { assertLineupGroupAlone, syncLineupItems } from "./lib/lineupItems"; import { LINEUPS_PAYLOAD_VERSION } from "./lib/payloadValidators"; import { errorWithCode, @@ -1337,6 +1338,7 @@ async function applyLineupOp( existing.pageId, ); } + await assertLineupGroupAlone(ctx, page._id, existing._id, payload); const revision = existing.revision + 1; await ctx.db.patch(existing._id, { pageId: page._id, @@ -1367,6 +1369,7 @@ async function applyLineupOp( existing.pageId, ); } + await assertLineupGroupAlone(ctx, page._id, null, payload); const now = Date.now(); await ctx.db.insert("lineups", { publicId, @@ -1478,6 +1481,14 @@ async function applyLineupOp( existing.pageId, ); } + if (patch.payload !== undefined) { + await assertLineupGroupAlone( + ctx, + existing.pageId, + existing._id, + patch.payload as LineupPayload, + ); + } const revision = existing.revision + 1; await ctx.db.patch(existing._id, { ...patch, @@ -1538,6 +1549,7 @@ async function reconcileExpectedAssets( } else { await syncLineupAssetReferences(ctx, row._id, row); await syncLineupAgents(ctx, row._id, row); + await syncLineupItems(ctx, row._id, row); } const now = Date.now(); const assetsBefore = referencedAssetIds(rowBefore); diff --git a/convex/schema.ts b/convex/schema.ts index 4904e2ad..7d274020 100644 --- a/convex/schema.ts +++ b/convex/schema.ts @@ -138,6 +138,18 @@ export default defineSchema({ }) .index("by_strategyId", ["strategyId"]) .index("by_lineupId", ["lineupId"]), + // The lineups and spots each live lineup group holds, one small row per + // item (`origin:`, `landing:` or `link:`), kept in step with + // every lineup write (see lib/lineupItems.ts). A write is refused if it + // would put an item in two groups of a page; the check reads these rows + // instead of the lineup rows, whose image lists can be large. + lineupItems: defineTable({ + pageId: v.id("pages"), + lineupId: v.id("lineups"), + item: v.string(), + }) + .index("by_pageId_and_item", ["pageId", "item"]) + .index("by_lineupId", ["lineupId"]), // Which content rows show which images: one small row per (element or // lineup row, image id it shows), kept in step with every content write // (see lib/assetReferences.ts). Media cleanup checks an image's references diff --git a/convex/strategies.ts b/convex/strategies.ts index 49208e8a..11c7e7f5 100644 --- a/convex/strategies.ts +++ b/convex/strategies.ts @@ -23,6 +23,7 @@ import { requireCurrentUser, } from "./lib/auth"; import type { StrategyRole } from "./lib/auth"; +import { lineupGroupItems } from "./lib/lineupItems"; import { getFolderByPublicId, getStrategyByPublicId, @@ -855,6 +856,11 @@ export const duplicate = mutation({ }); copiedLineupAgents.push({ pageId, ...agent }); } + const items = lineupGroupItems(copiedLineup.payload); + budget.spend({ documents: items.size }); + for (const item of items) { + await ctx.db.insert("lineupItems", { pageId, lineupId, item }); + } } for (const [targetAssetPublicId, sourceAssetPublicId] of diff --git a/convex/testContent.helpers.ts b/convex/testContent.helpers.ts index 0d50d64a..b3f262ba 100644 --- a/convex/testContent.helpers.ts +++ b/convex/testContent.helpers.ts @@ -10,6 +10,7 @@ import { syncLineupAssetReferences, } from "./lib/assetReferences"; import { syncLineupAgents } from "./lib/strategyAgentSummary"; +import { syncLineupItems } from "./lib/lineupItems"; export async function insertElement( ctx: MutationCtx, @@ -27,6 +28,7 @@ export async function insertLineup( const id = await ctx.db.insert("lineups", lineup); await syncLineupAssetReferences(ctx, id, lineup); await syncLineupAgents(ctx, id, lineup); + await syncLineupItems(ctx, id, lineup); return id; } diff --git a/lib/collab/cloud_lineup_rows.dart b/lib/collab/cloud_lineup_rows.dart index f4d3ed9d..b1e41aaf 100644 --- a/lib/collab/cloud_lineup_rows.dart +++ b/lib/collab/cloud_lineup_rows.dart @@ -15,6 +15,11 @@ import 'package:uuid/uuid.dart'; /// merge. That keeps every edit a write to exactly the row the user's /// lineups already live in, and no row is ever deleted except by deleting /// its last lineup. +/// +/// No two live rows of a page hold the same lineup or spot: the server +/// refuses a write that would make them (see assertLineupGroupAlone in +/// convex/ops.ts), so the work waits in attention rather than two rows +/// disagreeing about one spot. class CloudLineupRow { const CloudLineupRow({required this.publicId, required this.payload}); @@ -46,10 +51,9 @@ class CloudLineupRows { /// A page's lineups as its live [rows] describe them, rows in order. /// -/// Ids are unique within a row, and a lineup is in one row. A spot can be -/// in two (see [cloudLineupRows]): the first row to name a spot or lineup -/// draws it, and a lineup in a later row aimed at that spot is drawn to it -/// too. +/// Ids are unique within a row and, as the server keeps them, across the +/// page's rows. Should two rows name one anyway, the first draws it, and a +/// lineup in a later row aimed at that spot is drawn to it too. /// /// Throws a [FormatException] naming the row when one cannot be read. CloudLineups lineUpGraphFromCloudRows(Iterable rows) { @@ -99,9 +103,9 @@ CloudLineups lineUpGraphFromCloudRows(Iterable rows) { /// fresh id, when every one of them is). /// /// A row holds its lineups and every spot they aim at. The app only ever -/// aims a new lineup at one existing spot, so a spot is in one row; when -/// lineups of two groups do share one (only rows written elsewhere make -/// that), each row carries it, rather than one group swallowing the other. +/// aims a new lineup at one existing spot, so a spot is in one row. A +/// lineup aimed at spots of two groups would put a spot in two rows; the +/// server refuses that write, and the work waits in attention. /// /// A lineup whose origin or landing is missing draws nothing on the canvas, /// so it is not written; neither is a spot no lineup is aimed at. Rows come diff --git a/lib/providers/collab/active_page_live_sync_provider.dart b/lib/providers/collab/active_page_live_sync_provider.dart index 36af5964..dc713d65 100644 --- a/lib/providers/collab/active_page_live_sync_provider.dart +++ b/lib/providers/collab/active_page_live_sync_provider.dart @@ -1026,9 +1026,8 @@ class ActivePageLiveSyncNotifier extends Notifier { // has or had, so it never lands on a teammate's group or a deleted one. final freshLineupSortIndex = freshSortIndexes(EntitySyncKeyKind.lineup); final lineupGroupOf = _lineupGroupOfByPage[pageId] ??= {}; - final lineUpGraph = ref.read(lineUpProvider).graph; final lineupRows = cloudLineupRows( - lineUpGraph, + ref.read(lineUpProvider).graph, groupOf: lineupGroupOf, takenGroupIds: { for (final key in [ @@ -1051,35 +1050,6 @@ class ActivePageLiveSyncNotifier extends Notifier { deleted: false, ); } - // A group's row goes only when its last lineup does. A stored row none - // of the rows above is, while a lineup it holds is still drawn, had its - // lineups drawn from another row naming them too (only rows a build - // that merged groups wrote do that): it stays exactly as stored, never - // deleted and never rewritten with the other row's copy. - final drawnLineupIds = {for (final link in lineUpGraph.links) link.id}; - for (final MapEntry(:key, value: base) - in _hydratedBaseByEntityKey.entries) { - if (key.pageId != pageId || - key.kind != EntitySyncKeyKind.lineup || - base.deleted || - entities.containsKey(key)) { - continue; - } - final links = cloudPayloadData(base.payload)['links']; - final holdsDrawn = links is List && - links.any( - (link) => link is Map && drawnLineupIds.contains(link['id'])); - if (!holdsDrawn) continue; - entities[key] = _NormalizedEntity( - key: key, - overlayEntityType: ActivePageOverlayEntityType.lineup, - payload: base.payload, - sortIndex: knownSortIndex(key) ?? base.sortIndex, - revision: 0, - deleted: false, - ); - } - return entities; } diff --git a/lib/providers/collab/lineup_conflicts_provider.dart b/lib/providers/collab/lineup_conflicts_provider.dart index 5d20cd54..9ae437f0 100644 --- a/lib/providers/collab/lineup_conflicts_provider.dart +++ b/lib/providers/collab/lineup_conflicts_provider.dart @@ -46,7 +46,11 @@ final lineupConflictsProvider = Provider?>((ref) { final queue = ref.watch(strategyOpQueueProvider); final attention = queue.attentionByEntityKey; final page = ref.watch(remoteEditorSnapshotProvider).valueOrNull?.activePage; - if (attention.isEmpty || page == null) return null; + // Keep both saves a copy; while outbox records cannot be saved it is not + // offered. + if (attention.isEmpty || page == null || queue.hasDurabilityFailure) { + return null; + } final pageId = page.page.publicId; if (attention.keys.any( (key) => key.kind != EntitySyncKeyKind.lineup || key.pageId != pageId, diff --git a/lib/providers/collab/strategy_op_queue_provider.dart b/lib/providers/collab/strategy_op_queue_provider.dart index 8ab256a3..7ab3455c 100644 --- a/lib/providers/collab/strategy_op_queue_provider.dart +++ b/lib/providers/collab/strategy_op_queue_provider.dart @@ -1123,10 +1123,15 @@ class StrategyOpQueueNotifier extends Notifier { /// Each durable record contains both the rejected predecessor and any newer /// successor for that entity. Removing the record discards both, without /// changing unrelated queued, in-flight, paused, or rejected work. + /// + /// Given [onlyIf] (the refused op id and successor op id of each key, as + /// the user last saw them), a key whose waiting work has changed since is + /// left as it is: the user chose to discard that work, not a newer edit. Future> discardRejected( - Set entityKeys, - ) => - _dropAttention(entityKeys, adoptRemote: true); + Set entityKeys, { + Map? onlyIf, + }) => + _dropAttention(entityKeys, adoptRemote: true, onlyIf: onlyIf); /// Whether the server refused [key]'s waiting work because a teammate /// deleted what it edits (rejection reason `deleted`). @@ -1153,6 +1158,7 @@ class StrategyOpQueueNotifier extends Notifier { Future> _dropAttention( Set entityKeys, { required bool adoptRemote, + Map? onlyIf, }) { return _serializeWrite(() async { final attention = Map.from( @@ -1172,6 +1178,14 @@ class StrategyOpQueueNotifier extends Notifier { if (rejected == null || accountId == null || strategyPublicId == null) { continue; } + if (onlyIf != null && + onlyIf[key] != + ( + rejected.pending.op.opId, + successors[key]?.pending.op.opId, + )) { + continue; + } final storageKey = DurableOutboxRecord.createStorageKey( accountId: accountId, strategyPublicId: strategyPublicId, diff --git a/lib/providers/strategy_page_session_provider.dart b/lib/providers/strategy_page_session_provider.dart index fb49d199..142ae5aa 100644 --- a/lib/providers/strategy_page_session_provider.dart +++ b/lib/providers/strategy_page_session_provider.dart @@ -596,6 +596,9 @@ class StrategyPageSessionNotifier extends Notifier { key.pageId != activePageId)) { throw StateError('Keep both is only for lineup groups on this page'); } + // A copy is only kept if it can be saved; while this device cannot + // save outbox records, nothing is copied and nothing discarded. + if (queue.hasDurabilityFailure) return KeepBothOutcome.unchanged; final generation = _pageSessionGeneration; final waiting = _waitingWork(attention.keys); final copies = []; @@ -636,12 +639,11 @@ class StrategyPageSessionNotifier extends Notifier { _keptAsCopy .addAll(waiting.entries.map((entry) => (entry.key, entry.value))); await pageSource.flushCurrentPage(); - // A copy that could not be saved waits in attention itself. - final saved = ref - .read(strategyOpQueueProvider) - .attentionByEntityKey - .keys - .every(waiting.containsKey); + // A copy that could not be saved leaves the outbox failing (and waits in + // attention itself): nothing may be discarded on its behalf. + final afterCopy = ref.read(strategyOpQueueProvider); + final saved = !afterCopy.hasDurabilityFailure && + afterCopy.attentionByEntityKey.keys.every(waiting.containsKey); if (!saved || !unchangedSince()) return KeepBothOutcome.copiesOnly; return await _useCloudVersionsFor(waiting) ? KeepBothOutcome.kept @@ -688,14 +690,22 @@ class StrategyPageSessionNotifier extends Notifier { return false; } + final generation = _pageSessionGeneration; + bool waitingChanged() => + waiting != null && + (_disposed || + generation != _pageSessionGeneration || + !_sameWaitingWork(waiting)); + _isResolvingConflicts = true; try { - await _resolvePageSource(strategyId, StrategySource.cloud) - .flushCurrentPage(); + final currentPageSource = + _resolvePageSource(strategyId, StrategySource.cloud); + await currentPageSource.flushCurrentPage(); final strategyNotifier = ref.read(strategyProvider.notifier); strategyNotifier.consumeScheduledCloudPageSync(); strategyNotifier.consumeScheduledCloudStrategySync(); - if (waiting != null && !_sameWaitingWork(waiting)) return false; + if (waitingChanged()) return false; final rejected = Map.from( ref.read(strategyOpQueueProvider).attentionByEntityKey, )..removeWhere((key, _) => waiting != null && !waiting.containsKey(key)); @@ -707,6 +717,13 @@ class StrategyPageSessionNotifier extends Notifier { return false; } + // Edits made while the cloud's version loaded are saved, and one to + // the waiting work stops a resolution limited to it: the page drawn + // next would replace that edit on screen. + if (waiting != null) { + await currentPageSource.flushCurrentPage(); + if (waitingChanged()) return false; + } final targetPageId = _resolveHydrationTargetPage(snapshot); final localMetadata = _unsentLocalMetadata(); if (targetPageId != null) { @@ -735,7 +752,7 @@ class StrategyPageSessionNotifier extends Notifier { final discarded = await ref .read(strategyOpQueueProvider.notifier) - .discardRejected(rejected.keys.toSet()); + .discardRejected(rejected.keys.toSet(), onlyIf: waiting); // A failed durable delete keeps its overlay and conflict. Restore those // entities if only part of the requested adoption could be saved. if (discarded.length != rejected.length && targetPageId != null) { diff --git a/test/cloud_lineup_rows_test.dart b/test/cloud_lineup_rows_test.dart index 91dfa596..24c99dc1 100644 --- a/test/cloud_lineup_rows_test.dart +++ b/test/cloud_lineup_rows_test.dart @@ -177,7 +177,9 @@ void main() { expect(written.groupOf['c'], 'k9'); }); - test('a lineup joining two groups goes to one, and neither row goes', () { + test( + 'a lineup joining two groups goes to one, carrying the other\'s spot ' + '(a write the server refuses), and neither row goes', () { final read = lineUpGraphFromCloudRows(cloudLineupRows(_page()).rows); final bridged = LineUpGraph( origins: read.graph.origins, @@ -198,10 +200,8 @@ void main() { }); test('a lineup two rows name is drawn from the first, and kept there', () { - // A build that merged groups wrote k9 into k1's row, and was refused - // deleting k9's row; a teammate has edited k9 there since. Live sync - // writes k9's row back as stored while k9 is drawn (see - // _normalizedLocalEntities); the codec only draws one k9. + // The server keeps rows from overlapping; should two name k9 anyway, + // the reader draws one k9, from the first. CloudLineupRow row(String id, LineUpGraph graph) => CloudLineupRow( publicId: id, payload: cloudLineupsPayload({ diff --git a/test/strategy_op_queue_provider_test.dart b/test/strategy_op_queue_provider_test.dart index 4916c7ab..af451df2 100644 --- a/test/strategy_op_queue_provider_test.dart +++ b/test/strategy_op_queue_provider_test.dart @@ -3283,6 +3283,76 @@ void main() { OpRejectionReason.revisionMismatch.wireName); }); + test('a conditional discard drops only the work still as the user saw it', + () async { + final store = MemoryDurableStrategyOutboxStore(); + final repository = _TombstoneRepository(); + const first = ElementPatchOp( + opId: 'move-1', + elementPublicId: 'element-1', + pagePublicId: 'page-1', + payload: {'value': 'moved'}, + sortIndex: 4, + expectedElementRevision: 1, + ); + const second = ElementPatchOp( + opId: 'move-2', + elementPublicId: 'element-2', + pagePublicId: 'page-1', + payload: {'value': 'moved'}, + sortIndex: 5, + expectedElementRevision: 1, + ); + final firstKey = EntitySyncKey.forStrategyOp(first)!; + final secondKey = EntitySyncKey.forStrategyOp(second)!; + final (container, notifier) = + await refused(repository, store, [first, second]); + // What the user chose to discard: both refused edits, nothing behind. + final seen = { + firstKey: ('move-1', null), + secondKey: ('move-2', null), + }; + + // Then the user edits element-2 again; it waits behind the refusal. + await notifier.syncDesiredOpsForPage( + pageId: 'page-1', + desiredOpsByEntityKey: { + secondKey: const ElementPatchOp( + opId: 'newer', + elementPublicId: 'element-2', + pagePublicId: 'page-1', + payload: {'value': 'newer'}, + sortIndex: 5, + expectedElementRevision: 1, + ), + }, + clearMissing: false, + ); + expect( + container + .read(strategyOpQueueProvider) + .successorByEntityKey[secondKey]! + .pending + .op + .opId, + 'newer', + ); + + final discarded = + await notifier.discardRejected({firstKey, secondKey}, onlyIf: seen); + + // element-1's work is as seen, so it goes; element-2's changed, so it + // stays, the newer edit with it, in memory and on disk. + expect(discarded, {firstKey}); + final current = container.read(strategyOpQueueProvider); + expect(current.attentionByEntityKey.keys, [secondKey]); + expect(current.successorByEntityKey[secondKey]!.pending.op.opId, 'newer'); + final records = store.load().records; + expect([for (final record in records) record.entityKey], [secondKey]); + expect(records.single.pending.op.opId, 'move-2'); + expect(records.single.successorPending!.op.opId, 'newer'); + }); + test('discarding instead waits for the server copy to be adopted', () async { // The contrast that makes settling distinct: after Use cloud the diff --git a/test/strategy_page_session_provider_test.dart b/test/strategy_page_session_provider_test.dart index 47331bdc..832a9b1a 100644 --- a/test/strategy_page_session_provider_test.dart +++ b/test/strategy_page_session_provider_test.dart @@ -116,12 +116,15 @@ class _FakeRemoteEditorNotifier extends RemoteEditorSnapshotNotifier { /// as it is by then: a read still on its way. Completer? refreshGate; + /// While set, only a refresh made while this holds takes [refreshGate]. + bool Function()? gateWhen; + @override Future refresh() async { refreshCount += 1; final count = refreshCount; - final gate = refreshGate; - refreshGate = null; + final gate = (gateWhen?.call() ?? true) ? refreshGate : null; + if (gate != null) refreshGate = null; await gate?.future; final fails = readFails || (readFailsWhen?.call(count) ?? false); state = fails ? const AsyncData(null) : AsyncData(initialSnapshot); @@ -301,8 +304,9 @@ class _FakeStrategyOpQueueNotifier extends StrategyOpQueueNotifier { @override Future> discardRejected( - Set entityKeys, - ) async { + Set entityKeys, { + Map? onlyIf, + }) async { if (failDiscard) return {}; final attention = Map.from( state.attentionByEntityKey, @@ -310,7 +314,16 @@ class _FakeStrategyOpQueueNotifier extends StrategyOpQueueNotifier { final successors = Map.from( state.successorByEntityKey, ); - final discarded = attention.keys.toSet().intersection(entityKeys); + final discarded = { + for (final key in attention.keys.toSet().intersection(entityKeys)) + if (onlyIf == null || + onlyIf[key] == + ( + attention[key]!.pending.op.opId, + successors[key]?.pending.op.opId, + )) + key, + }; for (final key in discarded) { attention.remove(key); successors.remove(key); @@ -5102,61 +5115,6 @@ void main() { await _settle(); }); - test( - 'a row an older build merged a group into never costs the merged ' - 'group its row', () async { - final page = _page('page-1', 0); - // A build that merged groups wrote k9's lineup into k1's row and was - // refused deleting k9's; a teammate has edited k9's lineup since. - final merged = _groupRow( - page.publicId, - 'link-1', - origins: [_originJson('a'), _originJson('z', const Offset(70, 70))], - landings: [_landingJson('shared'), _landingJson('far')], - links: [ - _linkJson('link-1', originId: 'a', landingId: 'shared'), - _linkJson('link-5', originId: 'z', landingId: 'shared'), - _linkJson('link-9', originId: 'z', landingId: 'far', notes: 'old'), - ], - revision: 2, - ); - final teammates = _groupRow( - page.publicId, - 'link-9', - origins: [_originJson('z', const Offset(70, 70))], - landings: [_landingJson('far')], - links: [ - _linkJson('link-9', originId: 'z', landingId: 'far', notes: 'newer') - ], - revision: 3, - sortIndex: 1, - ); - final container = await openCloudPage(page, [merged, teammates]); - - // An unrelated edit writes no lineup row, and deletes none. - expect(lineupOpsAfterTextEdit(container, page), isEmpty); - - // Nor does an edit to the merged group delete the row it merged. - container.read(lineUpProvider.notifier).updateLink(container - .read(lineUpProvider) - .linkById('link-1')! - .copyWith(notes: 'mine')); - final ops = { - for (final MapEntry(:key, :value) in container - .read(activePageLiveSyncProvider.notifier) - .syncLocalPage( - strategyPublicId: 'cloud-strategy', - pageId: page.publicId, - )! - .entries) - if (key.kind == EntitySyncKeyKind.lineup) key: value, - }; - expect(ops.values.whereType(), isEmpty); - expect(ops.keys, - isNot(contains(EntitySyncKey.lineup(page.publicId, 'link-9')))); - await _settle(); - }); - test('a Paranoia saved at the old size opens corrected and sends nothing', () async { final page = _page('page-1', 0); @@ -7646,6 +7604,123 @@ void main() { expect(store.load().records, isEmpty); }); + test( + 'an edit to the conflicting group during Keep both\'s second cloud ' + 'read, after the copy, is neither discarded nor replaced', () async { + final page = _page('page-1', 0); + final (container, _, store) = await refusedBesideTeammate(page); + final key = keyOf(page, 'link-a'); + StrategyOpQueueState queueState() => + container.read(strategyOpQueueProvider); + List copies() => [ + for (final row in server.liveRows) + if (row.publicId != 'link-a') row, + ]; + // The copy's send waits, so no answer brings a read of its own; the + // read held is the one that loads the cloud's version to take it. + final send = repository.hold = Completer(); + final gate = liveRead.refreshGate = Completer(); + liveRead.gateWhen = + () => container.read(lineUpProvider).links.length == 4; + + final keepBoth = container + .read(strategyPageSessionProvider.notifier) + .keepBothForRejected(); + await _until(() => liveRead.refreshGate == null); + expect(onScreenOf(container), hasLength(4), reason: 'the copy is made'); + // While the cloud's version is on its way, the user edits the group. + container.read(lineUpProvider.notifier).updateLink(container + .read(lineUpProvider) + .linkById('link-b')! + .copyWith(notes: 'newer')); + // From here the canvas never shows the cloud's version in place of + // the edit, even for a moment. + var replaced = false; + container.listen(lineUpProvider, (_, next) { + if (next.linkById('link-b')?.notes != 'newer') replaced = true; + }); + gate.complete(); + + expect(await keepBoth, KeepBothOutcome.copiesOnly); + expect(replaced, isFalse); + send.complete(); + await _until(() => + queueState().queuedByEntityKey.isEmpty && + queueState().inFlightByEntityKey.isEmpty && + copies().length == 1); + for (var i = 0; i < 10; i++) { + await _settle(); + } + + // The newer edit is on screen and waits behind the refused one... + expect(container.read(lineUpProvider).linkById('link-b')!.notes, 'newer'); + expect(queueState().attentionByEntityKey.keys, [key]); + final newer = queueState().successorByEntityKey[key]!.pending.op; + expect( + _linkIn(newer.payload as CloudPayload, 'link-b')['notes'], 'newer'); + expect( + store.load().records.singleWhere((r) => r.entityKey == key).status, + DurableOutboxStatus.attention, + ); + // ...the cloud's group is untouched, and there is one copy. + expect(lineupsIn([server.row('link-a')]), + ['Heaven: remote lineup', 'Mid: theirs']); + expect(lineupsIn(copies()), ['Heaven: mine', 'Mid: remote lineup']); + expect(onScreenOf(container), [ + 'Heaven: mine', + 'Mid: newer', + 'Heaven: mine', + 'Mid: remote lineup', + ]); + }); + + test( + 'Keep both while the outbox still cannot save is not offered and ' + 'changes nothing', () async { + final page = _page('page-1', 0); + final store = _FailingOutboxStore(); + final (container, batches, _) = + await refusedBesideTeammate(page, store: store); + final key = keyOf(page, 'link-a'); + final session = container.read(strategyPageSessionProvider.notifier); + StrategyOpQueueState queueState() => + container.read(strategyOpQueueProvider); + final waiting = queueState().attentionByEntityKey[key]!.pending.op.opId; + // Only the copy's write fails, so nothing but lineup groups waits. + store.failPut = (record) => + record.entityKey.kind == EntitySyncKeyKind.lineup && + record.entityKey != key; + expect(await session.keepBothForRejected(), KeepBothOutcome.copiesOnly); + for (var i = 0; i < 10; i++) { + await _settle(); + } + expect(queueState().hasDurabilityFailure, isTrue); + final onScreen = onScreenOf(container); + expect(onScreen, hasLength(4)); + + // While writes still fail, the popover does not offer it... + expect(container.read(lineupConflictsProvider), isNull); + // ...and pressed anyway, it changes nothing. + expect( + queueState().attentionByEntityKey.keys, + everyElement(predicate( + (key) => key.kind == EntitySyncKeyKind.lineup))); + expect(await session.keepBothForRejected(), KeepBothOutcome.unchanged); + for (var i = 0; i < 10; i++) { + await _settle(); + } + expect(onScreenOf(container), onScreen); + expect( + queueState().pending.where((pending) => pending.op is LineupAddOp), + hasLength(1), + reason: 'only the first copy waits to be saved', + ); + expect(queueState().attentionByEntityKey[key]!.pending.op.opId, waiting); + expect(sentFor(batches, key), hasLength(1)); + expect(server.liveRows.map((row) => row.publicId), ['link-a']); + expect(container.read(lineupConflictsProvider), isNull); + }); + test( 'after Keep both, exporting the strategy to .ica and importing it ' 'brings back both versions', () async { From 990e9d83f43fdc5e203228b7223dbcfb539e1695 Mon Sep 17 00:00:00 2001 From: Dara Adedeji Date: Fri, 2 Oct 2026 19:19:37 -0400 Subject: [PATCH 10/14] Guard Use cloud like Keep both; confirm copies are queued From the third review of the group rows: - Use cloud resolves only the work waiting as the user saw it once the page's edits are saved, re-checks it right before redrawing, and discards conditionally, so an edit made while the cloud version loads is neither discarded nor drawn over. - Before redrawing, the canvas must be as it was before the last save: an edit made since stops the redraw (nothing awaits between the check and it). - Keep both discards nothing unless each copy's group is queued or on the server, so a save skipped without failing (edit access lost) cannot leave the copy only on the canvas. - A lineup group refused for overlapping another says so, instead of reading as a teammate conflict Keep mine could win. Co-Authored-By: Claude Opus 5.5 (1M context) --- lib/collab/cloud_sync_error_message.dart | 6 + lib/collab/collab_models.dart | 6 + .../collab/strategy_op_queue_provider.dart | 1 + .../strategy_page_session_provider.dart | 77 +++++- .../collab/cloud_sync_error_message_test.dart | 16 ++ test/strategy_op_queue_provider_test.dart | 45 +++- test/strategy_page_session_provider_test.dart | 239 +++++++++++++++++- test/widgets/cloud_sync_button_test.dart | 51 ++++ 8 files changed, 414 insertions(+), 27 deletions(-) diff --git a/lib/collab/cloud_sync_error_message.dart b/lib/collab/cloud_sync_error_message.dart index 733118b7..41e305ca 100644 --- a/lib/collab/cloud_sync_error_message.dart +++ b/lib/collab/cloud_sync_error_message.dart @@ -32,6 +32,7 @@ bool isSpecificAttentionReason(String error) { final lower = error.toLowerCase(); return lower.contains('too large for cloud sync') || lower.contains(lineupPageMismatchMessage.toLowerCase()) || + lower.contains(lineupOverlapMessage.toLowerCase()) || lower.contains(retiredLineupOpMessage.toLowerCase()) || lower.contains(teammateDeletedMessage.toLowerCase()) || lower.contains(pageDeletedMessage.toLowerCase()); @@ -129,6 +130,11 @@ String friendlyCloudSyncError(String raw) { 'this device. Reduce it, then choose Keep mine to retry, or Use ' 'cloud to drop it.'; } + if (lower.contains(lineupOverlapMessage.toLowerCase())) { + return 'This lineup shares a spot with lineups saved separately, so it ' + 'was not saved to the cloud. It remains on this device; Use cloud ' + 'removes it here.'; + } if (lower.contains(lineupPageMismatchMessage.toLowerCase())) { return 'This lineup clashes with one on another page, so it was not ' 'saved to the cloud. It remains on this device; Use cloud removes ' diff --git a/lib/collab/collab_models.dart b/lib/collab/collab_models.dart index b91ff044..2d95dd39 100644 --- a/lib/collab/collab_models.dart +++ b/lib/collab/collab_models.dart @@ -23,6 +23,12 @@ const cloudStrategyDeletedMessage = const lineupPageMismatchMessage = 'This lineup belongs to another page and cannot be moved'; +/// The server's message when it refuses a lineup group row holding a +/// lineup or spot another group of the page holds (see assertLineupGroupAlone +/// in convex/lib/lineupItems.ts). Sending it again cannot help. +const lineupOverlapMessage = + 'A lineup or spot in this group is already in another group'; + /// Marks an outbox record holding a lineup change in the cloud format before /// one row per lineup (an origin, landing or link row). No server takes it /// any more, so it is never sent; it waits in attention until the user diff --git a/lib/providers/collab/strategy_op_queue_provider.dart b/lib/providers/collab/strategy_op_queue_provider.dart index 7ab3455c..9d8804e4 100644 --- a/lib/providers/collab/strategy_op_queue_provider.dart +++ b/lib/providers/collab/strategy_op_queue_provider.dart @@ -2901,6 +2901,7 @@ class StrategyOpQueueNotifier extends Notifier { ? teammateDeletedMessage : lastError; if (reason == lineupPageMismatchMessage || + reason == lineupOverlapMessage || reason == teammateDeletedMessage || reason == retiredLineupOpMessage || reason == pageDeletedMessage) { diff --git a/lib/providers/strategy_page_session_provider.dart b/lib/providers/strategy_page_session_provider.dart index 142ae5aa..6e87f084 100644 --- a/lib/providers/strategy_page_session_provider.dart +++ b/lib/providers/strategy_page_session_provider.dart @@ -636,13 +636,32 @@ class StrategyPageSessionNotifier extends Notifier { for (final copy in copies) { lineUps.addRecovered(copy); } + final copyLineupIds = [ + for (final copy in copies) + if (copy.links.isNotEmpty) copy.links.first.id, + ]; _keptAsCopy .addAll(waiting.entries.map((entry) => (entry.key, entry.value))); await pageSource.flushCurrentPage(); // A copy that could not be saved leaves the outbox failing (and waits in // attention itself): nothing may be discarded on its behalf. final afterCopy = ref.read(strategyOpQueueProvider); - final saved = !afterCopy.hasDurabilityFailure && + final liveSync = ref.read(activePageLiveSyncProvider.notifier); + final queuedKeys = { + for (final pending in afterCopy.pending) + EntitySyncKey.forStrategyOp(pending.op), + }; + // Each copy's group must be queued (or already on the server): a save + // skipped without failing, say after edit access was lost, leaves the + // copy on the canvas alone. + final copiesQueued = copyLineupIds.every((lineupId) { + final group = liveSync.lineupGroupOf(activePageId!, lineupId); + if (group == null) return false; + final key = EntitySyncKey.lineup(activePageId, group); + return queuedKeys.contains(key) || liveSync.hydratedBase(key) != null; + }); + final saved = copiesQueued && + !afterCopy.hasDurabilityFailure && afterCopy.attentionByEntityKey.keys.every(waiting.containsKey); if (!saved || !unchangedSince()) return KeepBothOutcome.copiesOnly; return await _useCloudVersionsFor(waiting) @@ -666,6 +685,27 @@ class StrategyPageSessionNotifier extends Notifier { }; } + /// The canvas's content, one state object per kind of item on it. Every + /// edit replaces its kind's object, so comparing them by identity tells + /// whether anything changed in between. + List _canvasStates() => [ + ref.read(agentProvider), + ref.read(abilityProvider), + ref.read(drawingProvider), + ref.read(textProvider), + ref.read(placedImageProvider), + ref.read(utilityProvider), + ref.read(lineUpProvider), + ]; + + bool _sameCanvas(List saved) { + final now = _canvasStates(); + for (var i = 0; i < saved.length; i++) { + if (!identical(saved[i], now[i])) return false; + } + return true; + } + /// Whether exactly [waiting] still waits, nothing changed or gone. bool _sameWaitingWork(Map waiting) { final now = _waitingWork(waiting.keys); @@ -680,7 +720,9 @@ class StrategyPageSessionNotifier extends Notifier { Future useCloudVersionsForRejected() => _useCloudVersionsFor(null); /// [useCloudVersionsForRejected], for exactly the conflicts in [waiting] - /// when given: if they no longer wait as they were, nothing is discarded. + /// (by default, every conflict once the page's edits are saved). If they + /// no longer wait as they were, or the canvas changed after its last + /// save, nothing is discarded or redrawn: the newer work stays. Future _useCloudVersionsFor( Map? waiting, ) async { @@ -691,11 +733,11 @@ class StrategyPageSessionNotifier extends Notifier { } final generation = _pageSessionGeneration; + late Map resolving; bool waitingChanged() => - waiting != null && - (_disposed || - generation != _pageSessionGeneration || - !_sameWaitingWork(waiting)); + _disposed || + generation != _pageSessionGeneration || + !_sameWaitingWork(resolving); _isResolvingConflicts = true; try { @@ -705,10 +747,14 @@ class StrategyPageSessionNotifier extends Notifier { final strategyNotifier = ref.read(strategyProvider.notifier); strategyNotifier.consumeScheduledCloudPageSync(); strategyNotifier.consumeScheduledCloudStrategySync(); + resolving = waiting ?? + _waitingWork( + ref.read(strategyOpQueueProvider).attentionByEntityKey.keys, + ); if (waitingChanged()) return false; final rejected = Map.from( ref.read(strategyOpQueueProvider).attentionByEntityKey, - )..removeWhere((key, _) => waiting != null && !waiting.containsKey(key)); + )..removeWhere((key, _) => !resolving.containsKey(key)); if (rejected.isEmpty) return true; await ref.read(remoteEditorSnapshotProvider.notifier).refresh(); @@ -718,12 +764,12 @@ class StrategyPageSessionNotifier extends Notifier { } // Edits made while the cloud's version loaded are saved, and one to - // the waiting work stops a resolution limited to it: the page drawn - // next would replace that edit on screen. - if (waiting != null) { - await currentPageSource.flushCurrentPage(); - if (waitingChanged()) return false; - } + // the waiting work stops the resolution: the page drawn next would + // replace that edit on screen. The canvas is noted before that save, + // so an edit made while it is being written counts as a change too. + final savedCanvas = _canvasStates(); + await currentPageSource.flushCurrentPage(); + if (waitingChanged()) return false; final targetPageId = _resolveHydrationTargetPage(snapshot); final localMetadata = _unsentLocalMetadata(); if (targetPageId != null) { @@ -736,6 +782,9 @@ class StrategyPageSessionNotifier extends Notifier { targetPageId, discardedEntities: rejected.keys.toSet(), ); + // Nothing awaits between this check and the redraw: an edit since + // the last save is still only on the canvas, and stops it. + if (waitingChanged() || !_sameCanvas(savedCanvas)) return false; await _applyLoadedPageData( pageData, strategyId: strategyId, @@ -752,7 +801,7 @@ class StrategyPageSessionNotifier extends Notifier { final discarded = await ref .read(strategyOpQueueProvider.notifier) - .discardRejected(rejected.keys.toSet(), onlyIf: waiting); + .discardRejected(rejected.keys.toSet(), onlyIf: resolving); // A failed durable delete keeps its overlay and conflict. Restore those // entities if only part of the requested adoption could be saved. if (discarded.length != rejected.length && targetPageId != null) { diff --git a/test/collab/cloud_sync_error_message_test.dart b/test/collab/cloud_sync_error_message_test.dart index 65cfb549..bf96130b 100644 --- a/test/collab/cloud_sync_error_message_test.dart +++ b/test/collab/cloud_sync_error_message_test.dart @@ -56,6 +56,22 @@ void main() { expect(message, isNot(contains('Keep mine'))); }); + test('explains a lineup group refused for overlapping another', () { + final message = friendlyCloudSyncError(lineupOverlapMessage); + + expect(message, contains('shares a spot')); + expect(message, contains('not saved to the cloud')); + expect(message, contains('Use cloud')); + expect(message, isNot(contains(lineupOverlapMessage))); + expect(isSpecificAttentionReason(lineupOverlapMessage), isTrue); + // As the queue reports it beside other refused work. + expect( + isSpecificAttentionReason( + '$lineupOverlapMessage. $otherWorkNeedsAttentionNote'), + isTrue, + ); + }); + test('lineup refusals and oversized work are specific attention reasons', () { expect(isSpecificAttentionReason(lineupPageMismatchMessage), isTrue); expect(isSpecificAttentionReason(retiredLineupOpMessage), isTrue); diff --git a/test/strategy_op_queue_provider_test.dart b/test/strategy_op_queue_provider_test.dart index af451df2..5765c61f 100644 --- a/test/strategy_op_queue_provider_test.dart +++ b/test/strategy_op_queue_provider_test.dart @@ -966,6 +966,37 @@ void main() { ); }); + test( + 'a lineup group refused for overlapping another stays saved and keeps ' + 'that reason', () async { + final store = MemoryDurableStrategyOutboxStore(); + final container = _cloudQueueContainer( + store: store, + repository: _FailingLineupRepository( + code: 'INVALID_LINEUP_PAYLOAD_DATA', + message: lineupOverlapMessage, + ), + ); + addTearDown(container.dispose); + final notifier = container.read(strategyOpQueueProvider.notifier) + ..setActiveStrategy('strategy-1', accountId: 'account-a'); + final lineup = _lineupAdd(opId: 'add-group'); + final key = EntitySyncKey.forStrategyOp(lineup)!; + + await notifier.enqueue(lineup, flushImmediately: false); + await notifier.flushNow(); + + final current = container.read(strategyOpQueueProvider); + expect(current.attentionByEntityKey[key]!.pending.op.opId, 'add-group'); + expect(current.lastError, lineupOverlapMessage); + expect(isSpecificAttentionReason(current.lastError!), isTrue); + final durable = store.load().records.single; + expect(durable.status, DurableOutboxStatus.attention); + expect(durable.lastError, lineupOverlapMessage); + expect(friendlyCloudSyncError(durable.lastError!), + contains('shares a spot')); + }); + test('opening a strategy shows its saved lineup refusal', () async { final store = MemoryDurableStrategyOutboxStore(); await store.put(_savedRecord( @@ -3809,7 +3840,13 @@ class _RecordingAckRepository extends ConvexStrategyRepository { /// Refuses every op as the server refuses a lineup whose row lives on /// another page. class _FailingLineupRepository extends ConvexStrategyRepository { - _FailingLineupRepository() : super(IcarusConvexApi(_UnusedTransport())); + _FailingLineupRepository({ + this.code = 'LINEUP_PAGE_MISMATCH', + this.message = lineupPageMismatchMessage, + }) : super(IcarusConvexApi(_UnusedTransport())); + + final String code; + final String message; @override Future> applyBatch({ @@ -3822,9 +3859,9 @@ class _FailingLineupRepository extends ConvexStrategyRepository { for (final op in ops) FailedOpAck( opId: op.opId, - code: 'LINEUP_PAGE_MISMATCH', - rawCode: 'LINEUP_PAGE_MISMATCH', - message: lineupPageMismatchMessage, + code: code, + rawCode: code, + message: message, ), ]; } diff --git a/test/strategy_page_session_provider_test.dart b/test/strategy_page_session_provider_test.dart index 832a9b1a..ae14b06f 100644 --- a/test/strategy_page_session_provider_test.dart +++ b/test/strategy_page_session_provider_test.dart @@ -91,9 +91,13 @@ class _FakeRemoteEditorNotifier extends RemoteEditorSnapshotNotifier { state = AsyncData(snapshot); } + /// Runs as each page is asked for, before it is read. + void Function(String? pagePublicId)? onSetActivePage; + @override Future setActivePage(String? pagePublicId) async { selectedPageIds.add(pagePublicId); + onSetActivePage?.call(pagePublicId); if (pagePublicId == failingPageId) { throw StateError('Failed to load $pagePublicId'); } @@ -182,10 +186,7 @@ class _FakeStrategyOpQueueNotifier extends StrategyOpQueueNotifier { ); if (desiredOp != null && state.attentionByEntityKey.containsKey(entityKey)) { - successors[entityKey] = QueuedEntityIntent( - entityKey: entityKey, - pending: PendingOp(op: desiredOp, clientId: 'test-client'), - ); + _keepBehindRefusal(successors, entityKey, desiredOp); state = state.copyWith(successorByEntityKey: successors); return; } @@ -223,10 +224,7 @@ class _FakeStrategyOpQueueNotifier extends StrategyOpQueueNotifier { } for (final entry in desiredOpsByEntityKey.entries) { if (state.attentionByEntityKey.containsKey(entry.key)) { - successors[entry.key] = QueuedEntityIntent( - entityKey: entry.key, - pending: PendingOp(op: entry.value, clientId: 'test-client'), - ); + _keepBehindRefusal(successors, entry.key, entry.value); continue; } queued[entry.key] = QueuedEntityIntent( @@ -240,6 +238,34 @@ class _FakeStrategyOpQueueNotifier extends StrategyOpQueueNotifier { ); } + /// Like the real queue, keeps [desired] behind [key]'s refused op: work + /// the same as the refused op needs nothing behind it, and work the same + /// as the one already behind it leaves that one, op id and all. + void _keepBehindRefusal( + Map successors, + EntitySyncKey key, + StrategyOp desired, + ) { + bool same(StrategyOp left, StrategyOp right) => + left.kind == right.kind && + left.entityType == right.entityType && + left.entityPublicId == right.entityPublicId && + left.pagePublicId == right.pagePublicId && + cloudJsonEquivalent(left.payload, right.payload) && + left.sortIndex == right.sortIndex && + left.expectedRevision == right.expectedRevision; + if (same(state.attentionByEntityKey[key]!.pending.op, desired)) { + successors.remove(key); + return; + } + final behind = successors[key]?.pending.op; + if (behind != null && same(behind, desired)) return; + successors[key] = QueuedEntityIntent( + entityKey: key, + pending: PendingOp(op: desired, clientId: 'test-client'), + ); + } + @override Future flushNow() async { flushNowCount += 1; @@ -436,8 +462,14 @@ class _FakeStrategyOpQueueNotifier extends StrategyOpQueueNotifier { class _FailingOutboxStore extends MemoryDurableStrategyOutboxStore { bool Function(DurableOutboxRecord record)? failPut; + /// Runs as each write starts, before it lands: while it is on its way. + void Function(DurableOutboxRecord record)? onPut; + @override Future put(DurableOutboxRecord record) async { + onPut?.call(record); + // A real write leaves the event loop; so does this one. + await Future.delayed(Duration.zero); if (failPut?.call(record) ?? false) { throw const FileSystemException('The disk is full.'); } @@ -6936,8 +6968,10 @@ void main() { RemotePage page, { MemoryDurableStrategyOutboxStore? store, List otherPages = const [], + List elements = const [], }) async { - server = _FakeServer(page.publicId, lineups: [fanIn(page.publicId)]); + server = _FakeServer(page.publicId, + lineups: [fanIn(page.publicId)], elements: elements); store ??= MemoryDurableStrategyOutboxStore(); final (container, batches) = await openOnRealQueue(page, store: store, otherPages: otherPages); @@ -7721,6 +7755,193 @@ void main() { expect(container.read(lineupConflictsProvider), isNull); }); + test( + 'an edit to the conflicting group while Use cloud loads the cloud ' + 'version is neither discarded nor replaced', () async { + final page = _page('page-1', 0); + final (container, _, store) = await refusedBesideTeammate(page); + final key = keyOf(page, 'link-a'); + StrategyOpQueueState queueState() => + container.read(strategyOpQueueProvider); + final refreshes = liveRead.refreshCount; + final gate = liveRead.refreshGate = Completer(); + + final useCloud = container + .read(strategyPageSessionProvider.notifier) + .useCloudVersionsForRejected(); + await _until(() => liveRead.refreshCount > refreshes); + // While the cloud's version is on its way, the user edits the group. + container.read(lineUpProvider.notifier).updateLink(container + .read(lineUpProvider) + .linkById('link-b')! + .copyWith(notes: 'newer')); + var replaced = false; + container.listen(lineUpProvider, (_, next) { + if (next.linkById('link-b')?.notes != 'newer') replaced = true; + }); + gate.complete(); + + expect(await useCloud, isFalse); + for (var i = 0; i < 10; i++) { + await _settle(); + } + expect(replaced, isFalse); + expect(onScreenOf(container), ['Heaven: mine', 'Mid: newer']); + // The refused work and the newer edit behind it both still wait. + expect(queueState().attentionByEntityKey.keys, [key]); + final newer = queueState().successorByEntityKey[key]!.pending.op; + expect( + _linkIn(newer.payload as CloudPayload, 'link-b')['notes'], 'newer'); + final record = + store.load().records.singleWhere((r) => r.entityKey == key); + expect(record.status, DurableOutboxStatus.attention); + expect(record.successorPending!.op.opId, newer.opId); + expect( + lineupsIn(server.liveRows), ['Heaven: remote lineup', 'Mid: theirs']); + }); + + test( + 'Keep both whose copy is not saved because edit access went away ' + 'discards nothing', () async { + final page = _page('page-1', 0); + final (container, batches, store) = await refusedBesideTeammate(page); + final key = keyOf(page, 'link-a'); + StrategyOpQueueState queueState() => + container.read(strategyOpQueueProvider); + final waiting = queueState().attentionByEntityKey[key]!.pending.op.opId; + // The cloud read Keep both makes finds the user is a viewer now, so + // the page's edits, the copy included, are no longer saved. + final now = liveRead.initialSnapshot; + liveRead.initialSnapshot = _editorSnapshot( + pages: now.pages, + activePage: now.activePage!, + role: 'viewer', + ); + + final outcome = await container + .read(strategyPageSessionProvider.notifier) + .keepBothForRejected(); + for (var i = 0; i < 10; i++) { + await _settle(); + } + + expect(outcome, KeepBothOutcome.copiesOnly); + expect(queueState().hasDurabilityFailure, isFalse); + expect( + queueState().pending.where((pending) => pending.op is LineupAddOp), + isEmpty, + reason: 'the copy was never queued', + ); + // The waiting work is untouched, in memory and on disk. + expect(queueState().attentionByEntityKey[key]!.pending.op.opId, waiting); + expect( + store.load().records.singleWhere((r) => r.entityKey == key).status, + DurableOutboxStatus.attention, + ); + expect(sentFor(batches, key), hasLength(1)); + expect( + lineupsIn(server.liveRows), ['Heaven: remote lineup', 'Mid: theirs']); + expect(onScreenOf(container), contains('Heaven: mine')); + }); + + test( + 'a canvas edit after Use cloud\'s last save, while it loads the page, ' + 'stops the redraw', () async { + final page = _page('page-1', 0); + final (container, _, store) = await refusedBesideTeammate(page); + final key = keyOf(page, 'link-a'); + StrategyOpQueueState queueState() => + container.read(strategyOpQueueProvider); + // The cloud read comes back before the page itself, so loading the + // page waits for it: the one wait between the last save and the + // redraw. The user's edit lands there. + final now = liveRead.initialSnapshot; + liveRead.initialSnapshot = + RemoteEditorSnapshot(shell: now.shell, activePage: null); + var replaced = false; + var edited = false; + liveRead.onSetActivePage = (pageId) { + if (edited || pageId != page.publicId) return; + edited = true; + container.read(lineUpProvider.notifier).updateLink(container + .read(lineUpProvider) + .linkById('link-b')! + .copyWith(notes: 'later')); + container.listen(lineUpProvider, (_, next) { + if (next.linkById('link-b')?.notes != 'later') replaced = true; + }); + }; + + final resolved = await container + .read(strategyPageSessionProvider.notifier) + .useCloudVersionsForRejected(); + for (var i = 0; i < 10; i++) { + await _settle(); + } + + expect(edited, isTrue, reason: 'the edit landed in the window'); + expect(resolved, isFalse); + expect(replaced, isFalse); + expect(onScreenOf(container), ['Heaven: mine', 'Mid: later']); + expect(queueState().attentionByEntityKey.keys, [key]); + expect( + store.load().records.singleWhere((r) => r.entityKey == key).status, + DurableOutboxStatus.attention, + ); + expect( + lineupsIn(server.liveRows), ['Heaven: remote lineup', 'Mid: theirs']); + }); + + test( + 'an edit made while Use cloud\'s last save is being written stops the ' + 'redraw', () async { + final page = _page('page-1', 0); + final store = _FailingOutboxStore(); + final (container, _, _) = + await refusedBesideTeammate(page, store: store, elements: [ + _textElement(page.publicId, 'text-a', 'a'), + _textElement(page.publicId, 'text-b', 'b', sortIndex: 1), + ]); + String textOf(String id) => + container.read(textProvider).singleWhere((t) => t.id == id).text; + final refreshes = liveRead.refreshCount; + final gate = liveRead.refreshGate = Completer(); + + final useCloud = container + .read(strategyPageSessionProvider.notifier) + .useCloudVersionsForRejected(); + await _until(() => liveRead.refreshCount > refreshes); + // An edit made while the cloud's version loads gives the last save + // something to write... + container + .read(textProvider.notifier) + .updatePosition(const Offset(300, 300), 'text-a'); + // ...and while that write is on its way, the user types elsewhere. + var typed = false; + var replaced = false; + store.onPut = (record) { + if (typed || record.entityKey.entityId != 'text-a') return; + typed = true; + container.read(textProvider.notifier).commitText('text-b', 'typed'); + container.listen(textProvider, (_, next) { + if (next.singleWhere((t) => t.id == 'text-b').text != 'typed') { + replaced = true; + } + }); + }; + gate.complete(); + + final resolved = await useCloud; + for (var i = 0; i < 10; i++) { + await _settle(); + } + + expect(typed, isTrue, reason: 'the edit landed during the write'); + expect(replaced, isFalse, reason: 'the redraw put back the old text'); + expect(textOf('text-b'), 'typed'); + expect(resolved, isFalse); + }); + test( 'after Keep both, exporting the strategy to .ica and importing it ' 'brings back both versions', () async { diff --git a/test/widgets/cloud_sync_button_test.dart b/test/widgets/cloud_sync_button_test.dart index 5dd82626..807ecedd 100644 --- a/test/widgets/cloud_sync_button_test.dart +++ b/test/widgets/cloud_sync_button_test.dart @@ -1258,6 +1258,57 @@ void main() { }); } + testWidgets( + 'a lineup group the server refused as overlapping another says why, ' + 'not that a teammate changed it', (tester) async { + tester.view.physicalSize = const Size(1280, 1024); + tester.view.devicePixelRatio = 1; + addTearDown(tester.view.reset); + final queue = _AttentionOpQueue(1); + final container = _createConflictContainer( + queue: queue, + session: _ConflictSession(), + // Even with lineup changes to list, the specific reason comes first. + lineupConflicts: [ + LineupGroupConflict( + key: const EntitySyncKey.lineup('page-1', 'link-a'), + yours: const [LineupChange(label: 'Heaven', description: 'added')], + cloud: const [], + ), + ], + ); + addTearDown(container.dispose); + container + .read(strategySaveStateProvider.notifier) + .setCloudSyncError(lineupOverlapMessage); + + await tester.pumpWidget( + UncontrolledProviderScope( + container: container, + child: const ShadApp( + home: + Scaffold(body: CloudSyncButton(style: kEditorToolbarButtonStyle)), + ), + ), + ); + await tester.pump(); + await tester.tap(_syncButton('attention')); + await tester.pumpAndSettle(); + + expect( + find.textContaining('shares a spot with lineups saved separately'), + findsOneWidget, + ); + expect( + find.textContaining('Keep mine replaces their changes'), findsNothing); + expect( + find.textContaining('A teammate changed these lineups'), findsNothing); + expect(find.textContaining('Another edit reached'), findsNothing); + expect(find.text('Your changes'), findsNothing); + expect(find.text('Keep both'), findsNothing); + expect(find.text('Use cloud'), findsOneWidget); + }); + testWidgets( 'a lineup refusal among several changes says the choice covers all ' 'of them', (tester) async { From 4c6e2cf8d3ba4cd53e12516539040be32a92b0df Mon Sep 17 00:00:00 2001 From: Dara Adedeji Date: Fri, 2 Oct 2026 19:35:10 -0400 Subject: [PATCH 11/14] Check every Keep both copy each press; fence each redraw From the fourth review of the group rows: - Keep both remembers each copy it made, and every press checks all of them are queued before anything is discarded, so a retry cannot drop the original while an earlier copy is still unsaved. - Use cloud stops when its last save fails: the queue may not have recorded a newer edit, so nothing is discarded or drawn over it. - The canvas check runs where the redraw happens (after the page list it awaits), and the redraw after a partial discard has one too; a skipped redraw discards nothing. - An overlap refusal says Keep mine is refused while the spot is still shared, rather than hiding it (the user may have fixed the overlap, or other waiting work may need it). Two different specific refusals no longer read as one. Co-Authored-By: Claude Opus 5.5 (1M context) --- lib/collab/cloud_sync_error_message.dart | 5 +- .../collab/strategy_op_queue_provider.dart | 5 + .../strategy_page_session_provider.dart | 49 +++-- test/strategy_page_session_provider_test.dart | 186 +++++++++++++++++- test/widgets/cloud_sync_button_test.dart | 58 +++++- 5 files changed, 282 insertions(+), 21 deletions(-) diff --git a/lib/collab/cloud_sync_error_message.dart b/lib/collab/cloud_sync_error_message.dart index 41e305ca..b2a2b3c2 100644 --- a/lib/collab/cloud_sync_error_message.dart +++ b/lib/collab/cloud_sync_error_message.dart @@ -132,8 +132,9 @@ String friendlyCloudSyncError(String raw) { } if (lower.contains(lineupOverlapMessage.toLowerCase())) { return 'This lineup shares a spot with lineups saved separately, so it ' - 'was not saved to the cloud. It remains on this device; Use cloud ' - 'removes it here.'; + 'was not saved to the cloud. It remains on this device. Keep mine ' + 'sends it again, and is refused while the spot is still shared; Use ' + 'cloud removes it here.'; } if (lower.contains(lineupPageMismatchMessage.toLowerCase())) { return 'This lineup clashes with one on another page, so it was not ' diff --git a/lib/providers/collab/strategy_op_queue_provider.dart b/lib/providers/collab/strategy_op_queue_provider.dart index 9d8804e4..8d310f2a 100644 --- a/lib/providers/collab/strategy_op_queue_provider.dart +++ b/lib/providers/collab/strategy_op_queue_provider.dart @@ -2905,6 +2905,11 @@ class StrategyOpQueueNotifier extends Notifier { reason == teammateDeletedMessage || reason == retiredLineupOpMessage || reason == pageDeletedMessage) { + // A second, different reason is other work too: one explanation + // must not stand for both. + if (specificReason != null && reason != specificReason) { + hasOtherWork = true; + } specificReason ??= reason; } else { hasOtherWork = true; diff --git a/lib/providers/strategy_page_session_provider.dart b/lib/providers/strategy_page_session_provider.dart index 6e87f084..16f515a4 100644 --- a/lib/providers/strategy_page_session_provider.dart +++ b/lib/providers/strategy_page_session_provider.dart @@ -184,7 +184,9 @@ class StrategyPageSessionNotifier extends Notifier { /// Waiting work Keep both already put a copy of on the canvas, by /// [_waitingWork] entry: pressing it again finishes the job instead of /// copying the same version twice. - final Set<(EntitySyncKey, (String, String?))> _keptAsCopy = {}; + /// The copy's first lineup id, by entry, so every press can check the + /// copy is saved before anything is discarded. + final Map<(EntitySyncKey, (String, String?)), String> _keptAsCopy = {}; @override StrategyPageSessionState build() { @@ -602,6 +604,7 @@ class StrategyPageSessionNotifier extends Notifier { final generation = _pageSessionGeneration; final waiting = _waitingWork(attention.keys); final copies = []; + final copied = <(EntitySyncKey, (String, String?)), String>{}; for (final MapEntry(:key, value: refused) in attention.entries) { final newest = (queue.successorByEntityKey[key] ?? refused).pending.op; final payload = switch (newest) { @@ -609,12 +612,14 @@ class StrategyPageSessionNotifier extends Notifier { LineupPatchOp(:final payload?) => payload, _ => null, }; - if (payload == null || _keptAsCopy.contains((key, waiting[key]!))) { - continue; - } - copies.add(forkLineUpGraph(lineUpGraphFromCloudRows([ + final entry = (key, waiting[key]!); + if (payload == null || _keptAsCopy.containsKey(entry)) continue; + final copy = forkLineUpGraph(lineUpGraphFromCloudRows([ CloudLineupRow(publicId: key.entityId!, payload: payload), - ]).graph)); + ]).graph); + if (copy.links.isEmpty) continue; + copies.add(copy); + copied[entry] = copy.links.first.id; } bool unchangedSince() => !_disposed && @@ -636,12 +641,12 @@ class StrategyPageSessionNotifier extends Notifier { for (final copy in copies) { lineUps.addRecovered(copy); } + _keptAsCopy.addAll(copied); + // Every copy of this work, made now or by an earlier press. final copyLineupIds = [ - for (final copy in copies) - if (copy.links.isNotEmpty) copy.links.first.id, + for (final MapEntry(:key, :value) in waiting.entries) + if (_keptAsCopy[(key, value)] case final lineupId?) lineupId, ]; - _keptAsCopy - .addAll(waiting.entries.map((entry) => (entry.key, entry.value))); await pageSource.flushCurrentPage(); // A copy that could not be saved leaves the outbox failing (and waits in // attention itself): nothing may be discarded on its behalf. @@ -769,7 +774,12 @@ class StrategyPageSessionNotifier extends Notifier { // so an edit made while it is being written counts as a change too. final savedCanvas = _canvasStates(); await currentPageSource.flushCurrentPage(); - if (waitingChanged()) return false; + // A save that failed may have dropped a newer edit the queue never + // recorded; nothing is discarded or redrawn over it. + if (waitingChanged() || + ref.read(strategyOpQueueProvider).hasDurabilityFailure) { + return false; + } final targetPageId = _resolveHydrationTargetPage(snapshot); final localMetadata = _unsentLocalMetadata(); if (targetPageId != null) { @@ -782,13 +792,17 @@ class StrategyPageSessionNotifier extends Notifier { targetPageId, discardedEntities: rejected.keys.toSet(), ); - // Nothing awaits between this check and the redraw: an edit since - // the last save is still only on the canvas, and stops it. - if (waitingChanged() || !_sameCanvas(savedCanvas)) return false; + // An edit since the last save is still only on the canvas, and + // stops the redraw; checked again where the redraw happens, after + // the page list it awaits. + bool unchangedCanvas() => !waitingChanged() && _sameCanvas(savedCanvas); + if (!unchangedCanvas()) return false; + var redrawn = false; await _applyLoadedPageData( pageData, strategyId: strategyId, source: StrategySource.cloud, + canApply: () => redrawn = unchangedCanvas(), hydrationKey: _buildRemotePageHydrationKey(snapshot, targetPageId), preserveTextDrafts: true, loadedRemoteSnapshot: pageSource.loadedRemoteSnapshot, @@ -797,6 +811,7 @@ class StrategyPageSessionNotifier extends Notifier { ? null : localMetadata, ); + if (!redrawn) return false; } final discarded = await ref @@ -810,6 +825,8 @@ class StrategyPageSessionNotifier extends Notifier { strategyId: strategyId, activePageId: () => state.activePageId, ); + // Redrawn only if nothing was edited since the first redraw. + final redrawnCanvas = _canvasStates(); final pageData = await pageSource.loadAuthoritativePage( targetPageId, discardedEntities: discarded, @@ -818,6 +835,10 @@ class StrategyPageSessionNotifier extends Notifier { pageData, strategyId: strategyId, source: StrategySource.cloud, + canApply: () => + !_disposed && + generation == _pageSessionGeneration && + _sameCanvas(redrawnCanvas), preserveTextDrafts: true, loadedRemoteSnapshot: pageSource.loadedRemoteSnapshot, preservedMetadata: discarded.contains(const EntitySyncKey.strategy()) diff --git a/test/strategy_page_session_provider_test.dart b/test/strategy_page_session_provider_test.dart index ae14b06f..68f81e8d 100644 --- a/test/strategy_page_session_provider_test.dart +++ b/test/strategy_page_session_provider_test.dart @@ -603,6 +603,10 @@ class _ServerRepository implements ConvexStrategyRepository { /// answer is lost on its way back: the send fails as if offline. void Function()? loseNextAnswer; + /// While set, an op it answers for never reaches [server]; that answer + /// is returned instead, as a validation the server fails. + OpAck? Function(StrategyOp op)? refuse; + @override Future> applyBatch({ required String strategyPublicId, @@ -612,7 +616,9 @@ class _ServerRepository implements ConvexStrategyRepository { }) async { batches.add(ops); await hold?.future; - final acks = server.applyBatch(ops); + final acks = [ + for (final op in ops) refuse?.call(op) ?? server.apply(op), + ]; if (loseNextAnswer case final lose?) { loseNextAnswer = null; lose(); @@ -7942,6 +7948,184 @@ void main() { expect(resolved, isFalse); }); + test( + 'Keep both pressed again while the copy still cannot be saved ' + 'discards nothing and redraws nothing', () async { + final page = _page('page-1', 0); + final (container, batches, store) = await refusedBesideTeammate(page); + final key = keyOf(page, 'link-a'); + final session = container.read(strategyPageSessionProvider.notifier); + StrategyOpQueueState queueState() => + container.read(strategyOpQueueProvider); + final waiting = queueState().attentionByEntityKey[key]!.pending.op.opId; + // Keep both's reads find the user is a viewer now, so nothing they + // edit is saved, the copy included. + final now = liveRead.initialSnapshot; + liveRead.initialSnapshot = _editorSnapshot( + pages: now.pages, + activePage: now.activePage!, + role: 'viewer', + ); + expect(await session.keepBothForRejected(), KeepBothOutcome.copiesOnly); + for (var i = 0; i < 10; i++) { + await _settle(); + } + final withCopy = onScreenOf(container); + expect(withCopy, hasLength(4)); + + // Pressed again, still a viewer: the copy it remembers is still not + // saved, so the original is not discarded on its behalf. + var redrawn = false; + container.listen(lineUpProvider, (_, __) => redrawn = true); + expect(await session.keepBothForRejected(), KeepBothOutcome.copiesOnly); + for (var i = 0; i < 10; i++) { + await _settle(); + } + + expect(redrawn, isFalse); + expect(onScreenOf(container), withCopy); + expect(queueState().attentionByEntityKey[key]!.pending.op.opId, waiting); + expect( + store.load().records.singleWhere((r) => r.entityKey == key).status, + DurableOutboxStatus.attention, + ); + expect( + queueState().pending.where((pending) => pending.op is LineupAddOp), + isEmpty, + ); + expect(sentFor(batches, key), hasLength(1)); + expect( + lineupsIn(server.liveRows), ['Heaven: remote lineup', 'Mid: theirs']); + }); + + test( + 'an edit whose save fails while Use cloud loads the cloud version ' + 'stops it: the edit stays on screen and nothing is discarded', + () async { + final page = _page('page-1', 0); + final store = _FailingOutboxStore(); + final (container, batches, _) = + await refusedBesideTeammate(page, store: store); + final key = keyOf(page, 'link-a'); + StrategyOpQueueState queueState() => + container.read(strategyOpQueueProvider); + final waiting = queueState().attentionByEntityKey[key]!.pending.op.opId; + final refreshes = liveRead.refreshCount; + final gate = liveRead.refreshGate = Completer(); + + final useCloud = container + .read(strategyPageSessionProvider.notifier) + .useCloudVersionsForRejected(); + await _until(() => liveRead.refreshCount > refreshes); + // While the cloud's version is on its way, the user edits the group, + // and that edit cannot be written to the outbox. + store.failPut = (record) => record.entityKey == key; + container.read(lineUpProvider.notifier).updateLink(container + .read(lineUpProvider) + .linkById('link-b')! + .copyWith(notes: 'newer')); + var replaced = false; + container.listen(lineUpProvider, (_, next) { + if (next.linkById('link-b')?.notes != 'newer') replaced = true; + }); + gate.complete(); + + expect(await useCloud, isFalse); + for (var i = 0; i < 10; i++) { + await _settle(); + } + expect(queueState().hasDurabilityFailure, isTrue); + expect(replaced, isFalse); + expect(onScreenOf(container), ['Heaven: mine', 'Mid: newer']); + // The refused work is not discarded, in memory or on disk. + expect(queueState().attentionByEntityKey[key]!.pending.op.opId, waiting); + final record = + store.load().records.singleWhere((r) => r.entityKey == key); + expect(record.status, DurableOutboxStatus.attention); + expect(record.pending.op.opId, waiting); + expect(sentFor(batches, key), hasLength(1)); + expect( + lineupsIn(server.liveRows), ['Heaven: remote lineup', 'Mid: theirs']); + }); + + /// The server's refusal of lineup group [key] for overlapping another. + OpAck? Function(StrategyOp op) refuseAsOverlapping(EntitySyncKey key) => + (op) => EntitySyncKey.forStrategyOp(op) == key + ? FailedOpAck( + opId: op.opId, + code: 'INVALID_LINEUP_PAYLOAD_DATA', + rawCode: 'INVALID_LINEUP_PAYLOAD_DATA', + message: lineupOverlapMessage, + ) + : null; + + test( + 'a lineup group refused for overlapping another, alone, reaches the ' + 'sync button as exactly that reason', () async { + final page = _page('page-1', 0); + server = _FakeServer(page.publicId, lineups: [fanIn(page.publicId)]); + final (container, _) = await openOnRealQueue(page); + final key = keyOf(page, 'link-a'); + repository.refuse = refuseAsOverlapping(key); + + container.read(lineUpProvider.notifier).updateLink(container + .read(lineUpProvider) + .linkById('link-a')! + .copyWith(notes: 'mine')); + await _until(() => container + .read(strategyOpQueueProvider) + .attentionByEntityKey + .containsKey(key)); + for (var i = 0; i < 10; i++) { + await _settle(); + } + + expect(container.read(strategyOpQueueProvider).lastError, + lineupOverlapMessage); + expect(container.read(strategySaveStateProvider).cloudSyncError, + lineupOverlapMessage); + }); + + test( + 'an overlap refusal beside a teammate deletion Keep mine can restore ' + 'is not reported as the only reason', () async { + final page = _page('page-1', 0); + server = _FakeServer(page.publicId, lineups: [ + fanIn(page.publicId), + _lineup(page.publicId, 'd', sortIndex: 1), + ]); + final (container, _) = await openOnRealQueue(page); + final overlapping = keyOf(page, 'link-a'); + final deleted = keyOf(page, 'link-d'); + repository.refuse = refuseAsOverlapping(overlapping); + // A teammate deletes lineup d; the user edits both groups. + server.teammateDelete('link-d'); + final lineUps = container.read(lineUpProvider.notifier); + lineUps.updateLink(container + .read(lineUpProvider) + .linkById('link-a')! + .copyWith(notes: 'mine')); + lineUps.updateLink(container + .read(lineUpProvider) + .linkById('link-d')! + .copyWith(notes: 'mine')); + await _until(() => + container.read(strategyOpQueueProvider).attentionByEntityKey.length == + 2); + for (var i = 0; i < 10; i++) { + await _settle(); + } + expect( + container.read(strategyOpQueueProvider).attentionByEntityKey.keys, + unorderedEquals([overlapping, deleted]), + ); + + // Keep mine can bring lineup d back, so the overlap is not the only + // reason, and the sync button must not read as if it were. + expect(container.read(strategySaveStateProvider).cloudSyncError, + isNot(lineupOverlapMessage)); + }); + test( 'after Keep both, exporting the strategy to .ica and importing it ' 'brings back both versions', () async { diff --git a/test/widgets/cloud_sync_button_test.dart b/test/widgets/cloud_sync_button_test.dart index 807ecedd..a5e38be7 100644 --- a/test/widgets/cloud_sync_button_test.dart +++ b/test/widgets/cloud_sync_button_test.dart @@ -1269,11 +1269,11 @@ void main() { queue: queue, session: _ConflictSession(), // Even with lineup changes to list, the specific reason comes first. - lineupConflicts: [ + lineupConflicts: const [ LineupGroupConflict( - key: const EntitySyncKey.lineup('page-1', 'link-a'), - yours: const [LineupChange(label: 'Heaven', description: 'added')], - cloud: const [], + key: EntitySyncKey.lineup('page-1', 'link-a'), + yours: [LineupChange(label: 'Heaven', description: 'added')], + cloud: [], ), ], ); @@ -1309,6 +1309,56 @@ void main() { expect(find.text('Use cloud'), findsOneWidget); }); + for (final (name, error, keepMine) in [ + ('alone', lineupOverlapMessage, true), + ( + 'beside other work', + '$lineupOverlapMessage. $otherWorkNeedsAttentionNote', + true, + ), + ]) { + testWidgets( + 'an overlap refusal $name still offers Keep mine, saying when it ' + 'helps', (tester) async { + final queue = _AttentionOpQueue(keepMine ? 2 : 1); + final container = _createConflictContainer( + queue: queue, + session: _ConflictSession(), + ); + addTearDown(container.dispose); + container.read(strategySaveStateProvider.notifier).setCloudSyncError( + error, + ); + + await tester.pumpWidget( + UncontrolledProviderScope( + container: container, + child: const ShadApp( + home: Scaffold( + body: CloudSyncButton(style: kEditorToolbarButtonStyle), + ), + ), + ), + ); + await tester.pump(); + await tester.tap(_syncButton('attention')); + await tester.pumpAndSettle(); + + expect( + find.textContaining('shares a spot with lineups saved separately'), + findsOneWidget, + ); + expect( + find.textContaining('refused while the spot is still shared'), + findsOneWidget, + ); + expect(find.text('Use cloud'), findsOneWidget); + expect(find.text('Keep mine'), keepMine ? findsOneWidget : findsNothing); + expect(find.text('Retry sync'), findsNothing); + expect(find.text('Keep both'), findsNothing); + }); + } + testWidgets( 'a lineup refusal among several changes says the choice covers all ' 'of them', (tester) async { From 610bd70035563c0663c4fc52cf64d9124e7dfa37 Mon Sep 17 00:00:00 2001 From: Dara Adedeji Date: Fri, 2 Oct 2026 19:51:21 -0400 Subject: [PATCH 12/14] Fence the recovery redraw from the first; let Use cloud recover From the fifth review of the group rows: - The redraw after a partial discard compares the canvas with what the first redraw left, so an edit made while discarding is not drawn over. - Use cloud stops only when its own last save fails (the queue now counts persistence failures), so a failure left from an earlier discard no longer blocks it forever; and it reports no success when its save turned the conflicts back into queued work. - Keep both makes a copy again when the one an earlier press made is no longer on the canvas. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../collab/strategy_op_queue_provider.dart | 6 + .../strategy_page_session_provider.dart | 28 ++- test/strategy_page_session_provider_test.dart | 197 ++++++++++++++++++ 3 files changed, 225 insertions(+), 6 deletions(-) diff --git a/lib/providers/collab/strategy_op_queue_provider.dart b/lib/providers/collab/strategy_op_queue_provider.dart index 8d310f2a..14aacf0c 100644 --- a/lib/providers/collab/strategy_op_queue_provider.dart +++ b/lib/providers/collab/strategy_op_queue_provider.dart @@ -2359,7 +2359,13 @@ class StrategyOpQueueNotifier extends Notifier { bool get _hasDurabilityFailureForCurrentAccount => _hasDurabilityFailureForAccount(state.accountId); + /// How many times writing the outbox has failed, so a caller can tell + /// whether its own save failed rather than an earlier one. + int get persistenceFailureCount => _persistenceFailureCount; + int _persistenceFailureCount = 0; + void _recordPersistenceFailure(Object error, StackTrace stackTrace) { + _persistenceFailureCount++; log('Durable outbox persistence failed: $error', name: 'strategy_outbox', error: error, stackTrace: stackTrace); if (_isDisposed) return; diff --git a/lib/providers/strategy_page_session_provider.dart b/lib/providers/strategy_page_session_provider.dart index 16f515a4..3693c919 100644 --- a/lib/providers/strategy_page_session_provider.dart +++ b/lib/providers/strategy_page_session_provider.dart @@ -613,7 +613,13 @@ class StrategyPageSessionNotifier extends Notifier { _ => null, }; final entry = (key, waiting[key]!); - if (payload == null || _keptAsCopy.containsKey(entry)) continue; + if (payload == null) continue; + // A copy made by an earlier press stands, unless it is no longer on + // the canvas (the user deleted it): then this press makes it again. + if (_keptAsCopy[entry] case final copyId? + when ref.read(lineUpProvider).linkById(copyId) != null) { + continue; + } final copy = forkLineUpGraph(lineUpGraphFromCloudRows([ CloudLineupRow(publicId: key.entityId!, payload: payload), ]).graph); @@ -746,6 +752,8 @@ class StrategyPageSessionNotifier extends Notifier { _isResolvingConflicts = true; try { + final hadConflicts = + ref.read(strategyOpQueueProvider).attentionByEntityKey.isNotEmpty; final currentPageSource = _resolvePageSource(strategyId, StrategySource.cloud); await currentPageSource.flushCurrentPage(); @@ -760,7 +768,9 @@ class StrategyPageSessionNotifier extends Notifier { final rejected = Map.from( ref.read(strategyOpQueueProvider).attentionByEntityKey, )..removeWhere((key, _) => !resolving.containsKey(key)); - if (rejected.isEmpty) return true; + // Conflicts the save turned back into queued work (a record whose + // removal failed before, say) were not resolved: that is no success. + if (rejected.isEmpty) return !hadConflicts; await ref.read(remoteEditorSnapshotProvider.notifier).refresh(); final snapshot = ref.read(remoteEditorSnapshotProvider).valueOrNull; @@ -773,11 +783,15 @@ class StrategyPageSessionNotifier extends Notifier { // replace that edit on screen. The canvas is noted before that save, // so an edit made while it is being written counts as a change too. final savedCanvas = _canvasStates(); + final queueNotifier = ref.read(strategyOpQueueProvider.notifier); + final failuresBefore = queueNotifier.persistenceFailureCount; await currentPageSource.flushCurrentPage(); - // A save that failed may have dropped a newer edit the queue never - // recorded; nothing is discarded or redrawn over it. + // A save that failed just now may have dropped a newer edit the queue + // never recorded; nothing is discarded or redrawn over it. (A failure + // left from before, say a discard that did not finish, does not stop + // the discard that can clear it.) if (waitingChanged() || - ref.read(strategyOpQueueProvider).hasDurabilityFailure) { + queueNotifier.persistenceFailureCount != failuresBefore) { return false; } final targetPageId = _resolveHydrationTargetPage(snapshot); @@ -813,6 +827,9 @@ class StrategyPageSessionNotifier extends Notifier { ); if (!redrawn) return false; } + // What the first redraw left: a redraw after a partial discard below + // draws only over this, never over an edit made while discarding. + final redrawnCanvas = _canvasStates(); final discarded = await ref .read(strategyOpQueueProvider.notifier) @@ -826,7 +843,6 @@ class StrategyPageSessionNotifier extends Notifier { activePageId: () => state.activePageId, ); // Redrawn only if nothing was edited since the first redraw. - final redrawnCanvas = _canvasStates(); final pageData = await pageSource.loadAuthoritativePage( targetPageId, discardedEntities: discarded, diff --git a/test/strategy_page_session_provider_test.dart b/test/strategy_page_session_provider_test.dart index 68f81e8d..6225f801 100644 --- a/test/strategy_page_session_provider_test.dart +++ b/test/strategy_page_session_provider_test.dart @@ -475,6 +475,22 @@ class _FailingOutboxStore extends MemoryDurableStrategyOutboxStore { } await super.put(record); } + + /// While set, a removal fails when this holds for its storage key. + bool Function(String storageKey)? failRemove; + + /// Runs as each removal starts, before it lands. + void Function(String storageKey)? onRemove; + + @override + Future remove(String storageKey) async { + onRemove?.call(storageKey); + await Future.delayed(Duration.zero); + if (failRemove?.call(storageKey) ?? false) { + throw const FileSystemException('The disk is locked.'); + } + await super.remove(storageKey); + } } /// Plays the server's page restore: [onRestore] runs for each call, and @@ -8126,6 +8142,187 @@ void main() { isNot(lineupOverlapMessage)); }); + test( + 'a move made while Use cloud discards, when the discard partly ' + 'fails, is not drawn over by the redraw that follows', () async { + final page = _page('page-1', 0); + final store = _FailingOutboxStore(); + final (container, _, _) = await refusedBesideTeammate(page, + store: store, elements: [_textElement(page.publicId, 'text-a', 'a')]); + final key = keyOf(page, 'link-a'); + Offset textAt() => container + .read(textProvider) + .singleWhere((text) => text.id == 'text-a') + .position; + const moved = Offset(300, 300); + // Removing the refused work from the outbox fails; while it is on its + // way, the user moves the text. + var movedDuringDiscard = false; + store.onRemove = (storageKey) { + if (movedDuringDiscard) return; + movedDuringDiscard = true; + container.read(textProvider.notifier).updatePosition(moved, 'text-a'); + }; + store.failRemove = (storageKey) => true; + var redrawnOver = false; + + final useCloud = container + .read(strategyPageSessionProvider.notifier) + .useCloudVersionsForRejected(); + await _until(() => movedDuringDiscard); + container.listen(textProvider, (_, next) { + if (next.singleWhere((text) => text.id == 'text-a').position != moved) { + redrawnOver = true; + } + }); + + expect(await useCloud, isFalse); + for (var i = 0; i < 10; i++) { + await _settle(); + } + expect(redrawnOver, isFalse); + expect(textAt(), moved); + // The refused work still waits: its record could not be removed. + expect(container.read(strategyOpQueueProvider).attentionByEntityKey.keys, + [key]); + }); + + test( + 'Use cloud after storage recovers from a failed discard does not ' + 'claim success while the work goes out again, and the next press ' + 'resolves it', () async { + final page = _page('page-1', 0); + final store = _FailingOutboxStore(); + final (container, batches, _) = + await refusedBesideTeammate(page, store: store); + final key = keyOf(page, 'link-a'); + final session = container.read(strategyPageSessionProvider.notifier); + StrategyOpQueueState queueState() => + container.read(strategyOpQueueProvider); + + // The first press cannot remove the refused work from the outbox. + store.failRemove = (storageKey) => true; + expect(await session.useCloudVersionsForRejected(), isFalse); + for (var i = 0; i < 10; i++) { + await _settle(); + } + expect(queueState().attentionByEntityKey.keys, [key]); + expect(queueState().lastError, contains('could not be removed')); + expect(onScreenOf(container), ['Heaven: mine', 'Mid: remote lineup']); + + // Storage recovers. The record the removal left uncertain goes back + // to the send queue on the next save, so this press finds nothing to + // resolve; it says so rather than claim the cloud's version is shown. + store.failRemove = null; + expect(await session.useCloudVersionsForRejected(), isFalse); + expect(onScreenOf(container), ['Heaven: mine', 'Mid: remote lineup']); + + // The refused work goes out once more and is refused again: the + // teammate's version stands. + await _until(() => + sentFor(batches, key).length == 2 && + queueState().attentionByEntityKey.containsKey(key)); + for (var i = 0; i < 10; i++) { + await _settle(); + } + expect(sentFor(batches, key)[1].opId, sentFor(batches, key)[0].opId); + expect( + queueState() + .lastAckBatch + .singleWhere((acked) => acked.entityKey == key) + .ack, + isA().having((ack) => ack.rejectionReason, 'reason', + OpRejectionReason.revisionMismatch), + ); + expect(server.row('link-a').revision, 8); + expect( + lineupsIn(server.liveRows), ['Heaven: remote lineup', 'Mid: theirs']); + + // A further press takes the cloud's version. (The strategy's own + // details it saves on the way land too.) + expect(await session.useCloudVersionsForRejected(), isTrue); + await _until(() => queueState().pending.isEmpty); + for (var i = 0; i < 10; i++) { + await _settle(); + } + expect(onScreenOf(container), ['Heaven: remote lineup', 'Mid: theirs']); + expect(queueState().attentionByEntityKey, isEmpty); + expect(queueState().needsAttention, isFalse); + expect(queueState().pending, isEmpty); + expect(store.load().records, isEmpty); + expect(sentFor(batches, key), hasLength(2)); + }); + + test( + 'Keep both after the user deleted the copy an earlier press made ' + 'copies again and resolves, one copy in all', () async { + final page = _page('page-1', 0); + final (container, _, store) = await refusedBesideTeammate(page); + final session = container.read(strategyPageSessionProvider.notifier); + StrategyOpQueueState queueState() => + container.read(strategyOpQueueProvider); + List copies() => [ + for (final row in server.liveRows) + if (row.publicId != 'link-a') row, + ]; + List copyLinkIds() => [ + for (final link in container.read(lineUpProvider).links) + if (link.id != 'link-a' && link.id != 'link-b') link.id, + ]; + Future landed() async { + await _until(() => + queueState().queuedByEntityKey.isEmpty && + queueState().inFlightByEntityKey.isEmpty); + for (var i = 0; i < 10; i++) { + await _settle(); + } + } + + // The first press copies, then cannot load the cloud's version. + final first = liveRead.refreshCount + 1; + liveRead.readFailsWhen = (count) => count > first; + expect(await session.keepBothForRejected(), KeepBothOutcome.copiesOnly); + await landed(); + expect(copies(), hasLength(1)); + final firstCopy = copyLinkIds(); + expect(firstCopy, hasLength(2)); + + // The cloud is reachable again; the user deletes the copy, and the + // deletion lands. + liveRead.readFailsWhen = null; + showServer(); + await _settle(); + final lineUps = container.read(lineUpProvider.notifier); + for (final id in firstCopy) { + lineUps.deleteLink(id); + } + await _settle(); + await container.read(strategyOpQueueProvider.notifier).flushNow(); + await _until(() => copies().isEmpty); + await landed(); + expect(copyLinkIds(), isEmpty); + + // Pressed again with the cloud reachable: a fresh copy, and resolved. + expect(await session.keepBothForRejected(), KeepBothOutcome.kept); + await landed(); + + final copy = copies().single; + expect(lineupsIn([copy]), ['Heaven: mine', 'Mid: remote lineup']); + expect(copyLinkIds(), hasLength(2)); + expect(copyLinkIds().toSet().intersection(firstCopy.toSet()), isEmpty); + expect( + onScreenOf(container), + unorderedEquals([ + 'Heaven: remote lineup', + 'Mid: theirs', + 'Heaven: mine', + 'Mid: remote lineup', + ]), + ); + expect(queueState().attentionByEntityKey, isEmpty); + expect(store.load().records, isEmpty); + }); + test( 'after Keep both, exporting the strategy to .ica and importing it ' 'brings back both versions', () async { From 8448c04d8e4b91a79e0669abc12a9f256f5e80f0 Mon Sep 17 00:00:00 2001 From: Dara Adedeji Date: Fri, 2 Oct 2026 19:59:30 -0400 Subject: [PATCH 13/14] Remember every lineup of a Keep both copy; count settled conflicts From the sixth review of the group rows (no blockers): - Keep both remembers all of a copy's lineups, so deleting one of them does not make the next press copy the whole group again. - Use cloud whose save settles a conflict (gone on both sides) reports success; only conflicts turned back into queued work count against it. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../strategy_page_session_provider.dart | 43 +++++-- test/strategy_page_session_provider_test.dart | 111 ++++++++++++++++++ 2 files changed, 141 insertions(+), 13 deletions(-) diff --git a/lib/providers/strategy_page_session_provider.dart b/lib/providers/strategy_page_session_provider.dart index 3693c919..14afcc7a 100644 --- a/lib/providers/strategy_page_session_provider.dart +++ b/lib/providers/strategy_page_session_provider.dart @@ -184,9 +184,9 @@ class StrategyPageSessionNotifier extends Notifier { /// Waiting work Keep both already put a copy of on the canvas, by /// [_waitingWork] entry: pressing it again finishes the job instead of /// copying the same version twice. - /// The copy's first lineup id, by entry, so every press can check the - /// copy is saved before anything is discarded. - final Map<(EntitySyncKey, (String, String?)), String> _keptAsCopy = {}; + /// The copy's lineup ids, by entry, so every press can check the copy + /// is saved before anything is discarded. + final Map<(EntitySyncKey, (String, String?)), List> _keptAsCopy = {}; @override StrategyPageSessionState build() { @@ -604,7 +604,7 @@ class StrategyPageSessionNotifier extends Notifier { final generation = _pageSessionGeneration; final waiting = _waitingWork(attention.keys); final copies = []; - final copied = <(EntitySyncKey, (String, String?)), String>{}; + final copied = <(EntitySyncKey, (String, String?)), List>{}; for (final MapEntry(:key, value: refused) in attention.entries) { final newest = (queue.successorByEntityKey[key] ?? refused).pending.op; final payload = switch (newest) { @@ -614,10 +614,13 @@ class StrategyPageSessionNotifier extends Notifier { }; final entry = (key, waiting[key]!); if (payload == null) continue; - // A copy made by an earlier press stands, unless it is no longer on - // the canvas (the user deleted it): then this press makes it again. - if (_keptAsCopy[entry] case final copyId? - when ref.read(lineUpProvider).linkById(copyId) != null) { + // A copy made by an earlier press stands while any of its lineups is + // on the canvas; once the user deleted it all, this press makes it + // again. + if (_keptAsCopy[entry] case final copyIds? + when copyIds.any( + (id) => ref.read(lineUpProvider).linkById(id) != null, + )) { continue; } final copy = forkLineUpGraph(lineUpGraphFromCloudRows([ @@ -625,7 +628,7 @@ class StrategyPageSessionNotifier extends Notifier { ]).graph); if (copy.links.isEmpty) continue; copies.add(copy); - copied[entry] = copy.links.first.id; + copied[entry] = [for (final link in copy.links) link.id]; } bool unchangedSince() => !_disposed && @@ -649,9 +652,15 @@ class StrategyPageSessionNotifier extends Notifier { } _keptAsCopy.addAll(copied); // Every copy of this work, made now or by an earlier press. + final lineUpState = ref.read(lineUpProvider); final copyLineupIds = [ for (final MapEntry(:key, :value) in waiting.entries) - if (_keptAsCopy[(key, value)] case final lineupId?) lineupId, + if (_keptAsCopy[(key, value)] case final ids?) + // One surviving lineup names the copy's group. + ids.firstWhere( + (id) => lineUpState.linkById(id) != null, + orElse: () => ids.first, + ), ]; await pageSource.flushCurrentPage(); // A copy that could not be saved leaves the outbox failing (and waits in @@ -752,8 +761,9 @@ class StrategyPageSessionNotifier extends Notifier { _isResolvingConflicts = true; try { - final hadConflicts = - ref.read(strategyOpQueueProvider).attentionByEntityKey.isNotEmpty; + final conflictKeys = { + ...ref.read(strategyOpQueueProvider).attentionByEntityKey.keys, + }; final currentPageSource = _resolvePageSource(strategyId, StrategySource.cloud); await currentPageSource.flushCurrentPage(); @@ -770,7 +780,14 @@ class StrategyPageSessionNotifier extends Notifier { )..removeWhere((key, _) => !resolving.containsKey(key)); // Conflicts the save turned back into queued work (a record whose // removal failed before, say) were not resolved: that is no success. - if (rejected.isEmpty) return !hadConflicts; + // Ones it settled (gone on both sides) are. + if (rejected.isEmpty) { + final pending = { + for (final work in ref.read(strategyOpQueueProvider).pending) + EntitySyncKey.forStrategyOp(work.op), + }; + return !conflictKeys.any(pending.contains); + } await ref.read(remoteEditorSnapshotProvider.notifier).refresh(); final snapshot = ref.read(remoteEditorSnapshotProvider).valueOrNull; diff --git a/test/strategy_page_session_provider_test.dart b/test/strategy_page_session_provider_test.dart index 6225f801..484da0f6 100644 --- a/test/strategy_page_session_provider_test.dart +++ b/test/strategy_page_session_provider_test.dart @@ -8323,6 +8323,117 @@ void main() { expect(store.load().records, isEmpty); }); + test( + 'Keep both after the user deleted one lineup of the copy keeps the ' + 'rest of it and resolves without a second copy', () async { + final page = _page('page-1', 0); + final (container, _, store) = await refusedBesideTeammate(page); + final session = container.read(strategyPageSessionProvider.notifier); + StrategyOpQueueState queueState() => + container.read(strategyOpQueueProvider); + List copies() => [ + for (final row in server.liveRows) + if (row.publicId != 'link-a') row, + ]; + List copiedLinks() => [ + for (final link in container.read(lineUpProvider).links) + if (link.id != 'link-a' && link.id != 'link-b') link, + ]; + Future landed() async { + await _until(() => queueState() + .pending + .where((work) => !queueState() + .attentionByEntityKey + .values + .any((waiting) => waiting.pending.op.opId == work.op.opId)) + .isEmpty); + for (var i = 0; i < 10; i++) { + await _settle(); + } + } + + // The first press copies both lineups, then cannot load the cloud's + // version. + final first = liveRead.refreshCount + 1; + liveRead.readFailsWhen = (count) => count > first; + expect(await session.keepBothForRejected(), KeepBothOutcome.copiesOnly); + await landed(); + expect(lineupsIn(copies()), ['Heaven: mine', 'Mid: remote lineup']); + + // The cloud is reachable again; the user deletes one copied lineup. + liveRead.readFailsWhen = null; + showServer(); + await _settle(); + final heaven = copiedLinks().singleWhere((link) => link.name == 'Heaven'); + final mid = copiedLinks().singleWhere((link) => link.name == 'Mid'); + container.read(lineUpProvider.notifier).deleteLink(heaven.id); + await _settle(); + await container.read(strategyOpQueueProvider.notifier).flushNow(); + await _until(() => lineupsIn(copies()).length == 1); + await landed(); + + // Pressed again: the rest of the copy stands, nothing is copied again. + expect(await session.keepBothForRejected(), KeepBothOutcome.kept); + await landed(); + + expect(lineupsIn(copies()), ['Mid: remote lineup']); + expect(copiedLinks().map((link) => link.id), [mid.id]); + expect( + onScreenOf(container), + unorderedEquals( + ['Heaven: remote lineup', 'Mid: theirs', 'Mid: remote lineup']), + ); + expect(queueState().attentionByEntityKey, isEmpty); + expect(queueState().needsAttention, isFalse); + expect(store.load().records, isEmpty); + }); + + test( + 'Use cloud pressed before a deleted-on-both-sides refusal settles ' + 'counts its settling as resolved', () async { + final page = _page('page-1', 0); + server = + _FakeServer(page.publicId, lineups: [_lineup(page.publicId, 'a')]); + final store = MemoryDurableStrategyOutboxStore(); + final (container, batches) = await openOnRealQueue(page, store: store); + final key = _lineupKey(page.publicId, 'a'); + StrategyOpQueueState queueState() => + container.read(strategyOpQueueProvider); + + // A teammate deletes the lineup while the user edits it: refused as + // deleted. + server.teammateDelete('link-a'); + container.read(lineUpProvider.notifier).updateLink(container + .read(lineUpProvider) + .linkById('link-a')! + .copyWith(notes: 'mine')); + await refusedAsDeleted(container, batches, key); + + // The user deletes it too and, before that is saved, presses Use + // cloud: its save settles the refusal, both sides having it gone. + container.read(lineUpProvider.notifier).deleteLink('link-a'); + expect(queueState().attentionByEntityKey.keys, [key]); + expect( + await container + .read(strategyPageSessionProvider.notifier) + .useCloudVersionsForRejected(), + isTrue, + ); + for (var i = 0; i < 10; i++) { + await _settle(); + } + + expect(queueState().attentionByEntityKey, isEmpty); + expect(queueState().needsAttention, isFalse); + expect( + [for (final record in store.load().records) record.entityKey], + isNot(contains(key)), + ); + expect(container.read(lineUpProvider).links, isEmpty); + expect(server.row('link-a').deleted, isTrue); + expect(sentFor(batches, key), hasLength(1)); + }); + test( 'after Keep both, exporting the strategy to .ica and importing it ' 'brings back both versions', () async { From 9678b8202347e0ccb26f2b38ebf2f41773d9c103 Mon Sep 17 00:00:00 2001 From: Dara Adedeji Date: Fri, 2 Oct 2026 20:06:53 -0400 Subject: [PATCH 14/14] Regenerate Convex API types for lib/lineupItems Co-Authored-By: Claude Opus 5.5 (1M context) --- convex/_generated/api.d.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/convex/_generated/api.d.ts b/convex/_generated/api.d.ts index 3be9c511..a7f2c260 100644 --- a/convex/_generated/api.d.ts +++ b/convex/_generated/api.d.ts @@ -21,6 +21,7 @@ import type * as lib_cloudProtocol from "../lib/cloudProtocol.js"; import type * as lib_entities from "../lib/entities.js"; import type * as lib_errors from "../lib/errors.js"; import type * as lib_imageAssets from "../lib/imageAssets.js"; +import type * as lib_lineupItems from "../lib/lineupItems.js"; import type * as lib_opTypes from "../lib/opTypes.js"; import type * as lib_payloadValidators from "../lib/payloadValidators.js"; import type * as lib_profile from "../lib/profile.js"; @@ -59,6 +60,7 @@ declare const fullApi: ApiFromModules<{ "lib/entities": typeof lib_entities; "lib/errors": typeof lib_errors; "lib/imageAssets": typeof lib_imageAssets; + "lib/lineupItems": typeof lib_lineupItems; "lib/opTypes": typeof lib_opTypes; "lib/payloadValidators": typeof lib_payloadValidators; "lib/profile": typeof lib_profile;